0

Let's take some trivial react component that have to render:
1) text field (managed one, connected to state)
2) list of tasks (map through array connected to state too)
3) button, that adds new item to array from text field, when clicked

https://stackblitz.com/edit/react-glpzqn?embed=1&file=index.js

when text field is updated(textChange event) => then state is updated => that force render(on each key press in text field).

it's unwanted render, I only want to render when button is clicked, item is added to array and finally new list is rendered on a screen.

so I want to force render only on item added to the list, and not when text is changed.

some solutions that I can reveal are:
1) take input to another component so text change will not affect list rendering.
2) change input field to unmanaged and retrieve text manually when button clicked.

I'm guessing if there is some more elegant solution without changing a component? may be by using some HOC or same?

sergey muhlinin
  • 71
  • 1
  • 2
  • 9
  • 1
    You're not actually calling `forceUpdate` are you? furthermore.. you know that react isn't actually repainting the DOM everytime render is called.. you could be calling render at 60fps and if only the input is changing, only the input gets re-rendered, not everything on the page. lastly, a simple `shouldComponentUpdate` is all you need to save some cycles, though usually unenecessary – azium Mar 31 '18 at 14:58
  • but I want component's state update when text changed to synchronize field value, else it will not be changed. But when text field state changed it calls render, after each letter changed. I can't use shouldComponentUpdate, because I just want component update to occur after text changed, else it will prevent it from. You are right that only necessary parts are repainted, but reconciliation (checking what to update) occurs any way, and I don't want. So problem not in repainting but in recalculation of changes that I want prevent. – sergey muhlinin Mar 31 '18 at 16:32
  • Why do you want to prevent that? Are you making a high powered animation or something? That's very premature optimization. Anyways. Only children get reconciled when state changes, so you can model your tree such that only components whose state changes are rerendered. I think you should post some code otherwise its hard to write an accurate answer – azium Mar 31 '18 at 16:43
  • it's right, one solution is to take input field to its own component with own state, and so really list will not be rendered(and not repainted too), only on button click callback. but if we have list & input in one component may be there is some way to keep list render away until I click button without divide to 2 components apart? – sergey muhlinin Mar 31 '18 at 17:04
  • this's very simple control: https://stackblitz.com/edit/react-glpzqn?embed=1&file=index.js only to demonstrate what I mean – sergey muhlinin Mar 31 '18 at 17:24

2 Answers2

1

The simplest solution is using shouldComponentUpdate. Not on the entire component, but on the components you want not to render if props not relevant to them are changing:

class List extends Component {
  shouldComponentUpdate(nextProps) {
    return nextProps.items !== this.props.items
  }

  render() {
    // will only fire when the tasks array has changed
    console.log('list rendered')
    return this.props.items.map(item => (
      <div key={item}>{item}</div>
    ))
  }
}

This is just a shallow check too, which you get for free with PureComponent

class List extends PureComponent {
  render() {
    return this.props.items.map(item => (
      <div key={item}>{item}</div>
    ))
  }
}

All together:

import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';

class List extends PureComponent {
  render() {
    console.log('list rendered')
    return this.props.items.map(item => (
      <div key={item}>{item}</div>
    ))
  }
}

class App extends Component {
  // do not need constructor if already using class arrows
  state = {
    input: "",
    tasks: []
  };

  addTask = () => {
    this.setState({
      tasks: [...this.state.tasks, this.state.input],
      input: ""
    });
  }

  setValue = event => {
    this.setState({
      input: event.target.value
    });
  }

  render() {
    return (
      <div>
        <List items={this.state.tasks} />
        <input onChange={this.setValue} value={this.state.input} />
        <button onClick={this.addTask}>Add</button>
      </div>
    );
  }
}

render(<App />, document.getElementById('root'));

stackblitz solution

azium
  • 20,056
  • 7
  • 57
  • 79
  • Thanks! all it is 100%! furthermore we can keep them both in one component & change input to unmanaged. It's 2 solutions I assume. But may be exists some more elegant one? (without parting into 2 components or throwing input to unmanaged state) – sergey muhlinin Mar 31 '18 at 18:44
  • An arrow function uses the lexical `this`. Why are you overcomplicating the example by providing unsupported syntax that doesn't make sense? –  Jan 13 '19 at 21:08
  • @WillHoskings unsupported by whom? JSX is completely unsupported by all browsers already... using class arrow properties in React code is completely common, even the official docs suggest doing so. furthermore, now everything is moving towards hooks and classes in react will fade into obscurity so this discussion won't matter – azium Jan 14 '19 at 04:22
  • 1
    @WillHoskings Maybe you could simply elaborate. Either arrow functions are required in render, if the not the class methods need to be bound in the constructor, OR class methods are defined as properties using arrow functions. I chose the latter since it is the most common – azium Jan 14 '19 at 20:03
  • **An arrow function uses the lexical `this`** is it sinking in now –  Jan 14 '19 at 23:34
  • @WillHoskings that's precisely why you need to use an arrow function in a class method when using React. because when a class method is invoked from an event handler like `onClick={this.method}` then `this` refers to the HTMLElement and `this.setState` will throw `setState is not a function`. if you use an arrow function as a class property then the lexical `this` will refer to the React component and – azium Jan 15 '19 at 00:12
  • @WillHoskings this article is by dan abramov, one of the core react devs explaining exactly what I mean. https://overreacted.io/how-does-setstate-know-what-to-do/ . on a side note, you might want to reconsider your tone when participating in this type of discussion. you never know what bridges you might be burning. treat every one of these discussions as if your potential future employee is reading and you might get the opportunity of a lifetime. all the best – azium Jan 15 '19 at 00:18
  • @WillHoskings https://stackoverflow.com/questions/48699573/correct-use-of-arrow-functions-in-react – azium Jan 15 '19 at 00:36
  • So about my other question. Why are you overcomplicating the example by providing unsupported syntax that doesn't make sense? Why are you even using this? –  Jan 15 '19 at 17:59
  • My answer is using the same syntax in the OP's stackblitz example. If I used a different syntax than OP, then it would be more complicated, not less. Furthermore, it is supported by Babel, just like JSX. – azium Jan 15 '19 at 18:51
  • It also has performance disadvantages, because `X = () => {` in a class declaration is desugared to an assignment every time an instance is created, which is unnescesary. Why not just use `X(){`, which is shorter, **and is faster when transpiled?**. –  Jan 23 '19 at 22:52
  • When I write answers to stack overflow questions, I do not rewrite the original poster's code if I don't need to. I write it in their style and using their features so as long as I answer the actual question. If I made every suggestion to improve it then I would start with, never write a class in React. use hooks instead. – azium Jan 24 '19 at 04:57
1

Just replace onChange with onBlur like below, the below change would only render as soon you leave the input.

  <input onBlur={this.setValue} />
0xFK
  • 2,433
  • 32
  • 24