Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

forceUpdate() vs. this.setState() with a callback

Tags:

reactjs

The reason for this is when dealing with state variables that have their own functionality. There are two ways I can think of. One is to mutate this.state directly (well, sort of, I know it's discouraged) by calling the state functions, then call forceUpdate() and deal with what that brings. The other is to do the callback function. Which is the proper way?

Here's an example of what I am talking about. I'm working with Vis.js, and their DataSet object allows you to add data to an existing data set - nodes and edges, for example.

  addDataToGraph: function(data) {
    // Is this the proper way to do something like this?
    this.state.graphData.nodes.add(nodes);
    this.state.graphData.edges.add(edges);
    this.forceUpdate();

    // Or is this the right way - the question I am posing here
    this.setState({
      currentChunk: data.currentChunk,
      totalChunks: data.totalChunks
    }, function() {
      this.state.graphData.nodes.add(data.nodes);
      this.state.graphData.edges.add(data.edges);
    }.bind(this));
  }
like image 520
Andrew Bowler Avatar asked Feb 09 '16 04:02

Andrew Bowler


2 Answers

You definitely should not mutate this.state, or anything in it, ever. From the React Component API docs:

NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.

Even your second example isn't correct here, since you're still mutating this.state. The second parameter is merely a callback for when the state has been updated. What you want to do is somehow get new objects and give those to setState. Not being very familiar with Vis.js, this seems to be kind of tricky since it doesn't seem to have any operations that return entirely new/immutable data sets. There aren't even copy or clone operations. It looks like you'd have to actually make an entirely new vis.DataSet each time and add the data from the original, then add the new data, then this.setState( { graphData: newGraphData, ... } )

Ultimately that will probably be the 'pure' and correct way to do this.

All that said, and with a big YMMV caveat - since there's no apparent way to treat vis.DataSets as immutable, you can probably just go with the first option.

Take that first part of the warning in the docs:

calling setState() afterwards may replace the mutation you made

What will happen in that case? Nothing, since the actual object in your state is mutable, it's just the one reference forever. If React 'replaces it' it will just be replacing it with itself. In other words, as long as you never call setState with an entirely new DataSet, it won't get replaced with something unexpected at an unexpected time.

The big problem here will be that calling forceUpdate will, well, force a re-render, and that could be slow. But it mostly sounds like you want it to re-render no matter what anyway when that function is called, so... pragmatically speaking you're probably ok :)

like image 51
rfunduk Avatar answered Nov 03 '22 00:11

rfunduk


To mutate the state of an component we should always use this.setState() because it tells React that something has change in component state and then React will take care of re-render in performant way. However, we can also mutate the state object by using this.state.yourProperty=somethingNew but we should never ever do this because its too show and React is unaware of our state mutations so we have to explicitly trigger the re-render by calling this.forceUpdate(). A good example of using this.forceUpdate is the integration with other library like D3.js. D3 has its own data sets so assume that if something changes in D3 data set then it need to tell React that 'Hey! React I change some of my stuff and now I need to just re-render' so D3 will callback out react by using this.forceUpate().

like image 34
arvindsc Avatar answered Nov 03 '22 02:11

arvindsc