3

When using PureComponents you have the advantage over functional components that the component is not always rendered when the parent updates. It actually only renders when there is a change in the component props, which in this example is only when you change the input.

How could you pass an object to the PureComponent without it breaking all the advantage of using this kind of component?

Does this mean that as soon as you have a prop that you expect it to be of type object, it's better to just make your component functional?

I added an example to illustrate what I mean... (Probably you need to open it in a new window, as there are a lot of different children)

class TestPureComponent extends React.PureComponent {
  render() {
    return <div style={{border: '1px solid black'}}>{this.props.text} : {this.props.inputValue} {Math.random()} <button onClick={() => {this.props.dismissClicked()}}>Click me</button>  </div>
  }
}

function TestFunctionalComponent () {
  return <div style={{border: '1px solid black'}}>I always update as I am a functional component {Math.random()}</div>
}

const hell = () => {console.log('Logs hello')}

class RenderExample extends React.Component {
  constructor (props) {
    super(props)
    this.state = {clicked: false, inputValue: 'inputValue'}
    this.onClick = this.onClick.bind(this)
    this.doSomething = this.doSomething.bind(this)
  }

  onClick () {
    this.setState({clicked: !this.state.clicked})
  }
  
  doSomething () {
    console.log('helllllo')
  }
  
  heee = () => {
    console.log('heeeee')
  }

  render () {
    return <div>
      <button onClick={this.onClick}>
        Update state (forces re-render) {this.state.clicked && 'clicked'}
      </button>
      <input onChange={(e) => this.setState({inputValue: e.target.value})} type="text" value={this.state.inputValue}/>
      <br/>
      <br/>
      <TestFunctionalComponent />
      <br/>
      <TestPureComponent dismissClicked={() => hell} inputValue={this.state.inputValue} text="If you click the button this will re-render, if you change the input this will re-render"/>
      <br/>
      <TestPureComponent text="If you click the button this will NOT re-render, if you change the input this will re-render" dismissClicked={this.doSomething} inputValue={this.state.inputValue}/>
      <br/>
      <TestPureComponent text="If you click the button this will NOT re-render, if you change the input this will re-render" dismissClicked={this.heee} inputValue={this.state.inputValue}/>
      <br/>
      <TestPureComponent text="If you click the button this will NOT re-render, if you change the input this will re-render" dismissClicked={hell} inputValue={this.state.inputValue}/>
      <br/>
      <TestPureComponent text="If you click the button this will NOT re-render, if you change the input this will re-render" dismissClicked={hell} inputValue={this.state.inputValue}/>
      <br/><br/>
      <div> we will now add an inline object to each component and now they all update</div>
      
      <TestPureComponent dismissClicked={() => hell} inlineOBJ={{hello: 'world'}} inputValue={this.state.inputValue} text="If you click the button this will re-render, if you change the input this will re-render"/>
      <br/>
      <TestPureComponent text="If you click the button this will re-render, if you change the input this will re-render" inlineOBJ={{hello: 'world'}} dismissClicked={this.doSomething} inputValue={this.state.inputValue}/>
      <br/>
      <TestPureComponent text="If you click the button this will re-render, if you change the input this will re-render" inlineOBJ={{hello: 'world'}} dismissClicked={this.heee} inputValue={this.state.inputValue}/>
      <br/>
      <TestPureComponent text="If you click the button this will re-render, if you change the input this will re-render" inlineOBJ={{hello: 'world'}} dismissClicked={hell} inputValue={this.state.inputValue}/>
      <br/>
      <TestPureComponent text="If you click the button this will re-render, if you change the input this will re-render" inlineOBJ={{hello: 'world'}} dismissClicked={hell} inputValue={this.state.inputValue}/>
    </div>
  }
}

ReactDOM.render(<RenderExample />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

What I want is to see if there is a way to have a PureComponent, that wouldn't ALWAYS update when there is an OBJECT passed in as a prop.

Alejandro Vales
  • 2,816
  • 22
  • 41
  • 'When using PureComponents you have the advantage over functional components that the component is not always rendered when the parent updates.' It doesn't work that way. Difference between PureComponent and Component is that first one doesn't have `shouldComponentUpdate` method, instead implemneting it under the hood with shallow comparison. I don't fully understand what you are trying to achieve. – Andrzej Ziółek Aug 29 '19 at 08:19
  • try `const TestFunctionalComponent = React.memo(function(){...)` and you'll see it never updates. Functional components can be used with a [container](https://stackoverflow.com/a/57663595/1641941) so props never change reference if values don't change even though you pass functions/objects. If your prop is a calculated object I suggest using a container and React.memo. – HMR Aug 29 '19 at 08:25
  • 1
    @AndrzejZiółek What I wanted was to have a PureComponent, that wouldn't ALWAYS update when there is an OBJECT passed in as a prop (I'll try to reformulate the question) – Alejandro Vales Aug 29 '19 at 08:27
  • 1
    so don't construct object inline in `render` - this way it will be referentially different each time. – skyboyer Aug 29 '19 at 08:27
  • @skyboyer probably that is the only fix right? Have the object coming from the state or from the outside. – Alejandro Vales Aug 29 '19 at 08:29
  • 1
    As skyboyer suggested, don't construct the object in render. If you have to calculate or merge values to create props then use a container and `React.memo` that. You need a container if you render props like `onDelete={createDelete(id)}` or `onDelete={()=>anything}` or `item={{...dataItem,...editItem}}` or `item={{...item,updating}}` Look for [react-redux-connect](https://react-redux.js.org/api/connect) or [hooks](https://reactjs.org/docs/hooks-intro.html) or [react-redux-hooks](https://react-redux.js.org/next/api/hooks) for how to write containers. – HMR Aug 29 '19 at 08:53
  • @HMR if you want you can make an answer out of that comment – Alejandro Vales Aug 29 '19 at 09:16
  • It's ok, Domino's aswer is pretty good.If you have a practical example of components re rendering when values didn't change you can post it on SO. Your current example is a bit contrived. If you want to give me points you can upvote [this answer](https://stackoverflow.com/a/57663595/1641941) as that is a practical solution to prevent re rendering in a project using hooks. – HMR Aug 29 '19 at 09:29

1 Answers1

4

You have to store that object somewhere so that it does not get generated in each rerender. That also applies to functions which are generate during render (dismissClicked={() => hell}).

The functions should be outside if the render function so that they don't get created for every render like this: dismissClicked={this.hell}

For objects, just save it in the state.

You can achieve exactly the same with functional components. Use memo to wrap the component to enable a shallow comparison.

To prevent function generation on each render use useCallback and useState to save the object you are passing to the children so the references stay the same.

You can update these objects with useEffect easily.

Hope this helps. Happy coding.

Domino987
  • 8,475
  • 2
  • 15
  • 38