React and d3.js, how to ensure d3 component renders upon `.setState()`

Viewed 315

There is a pretty good tutorial on react + d3.js that I'm using here. I simply passed data from my state to the BarChart.js file; I also simplified the data set to just a handful of observations and changed the x axis to quarters (instead of years). Here is the relevant code of my app.js:

  state = { 
     data: [
      {quarter: "2Q20", aum: 100},
      {quarter: "3Q20", aum: 200},
      {quarter: "4Q20", aum: 300},
      {quarter: "1Q21", aum: 400},
      {quarter: "2Q21", aum:500}
    ],
    aum:934
 }


  handleAUM = e => {
    const aum = e.target.ariaValueNow;
    this.setState({aum});


    const data = [...this.state.data];
    const index = 4;
    data[index].aum = e.target.ariaValueNow;
    this.setState({data});
  }

render() {
    return (
        <div>
            <DiscreteSlider 
               aum={this.state.aum}
               handleAUM={this.handleAUM}
            />
            <h2>USD{this.state.aum}bn</h2>
            <BarChart data={this.state.data} />
        </div>
        )
    }  

Essentially, what's supposed to happen is the user can toggle a slider which updates the value of the state. For the scope of this question, let's focus on the last item in the data array in the state: {quarter: "2Q21", aum:500}. After checking the console log, the slider is in fact updating the state -- that is to say the user can drag the slider up all the way to 2000 and the value for aum will be updated to that amount. See handleAUM function for how I'm slicing the 4th item from the data array.

In native d3, once we update the data, the svg will change too (can even add animations to make it smooth, but leaving that out for now). However, what we have here is that the state is being updated but the d3 barchart is not rendering these updates.

My understanding is that react's universal rendering of the state would sidestep these sorts of issues. Perhaps I'm not passing the props down correctly.

What's supposed to happen is the very last (right-most on x-axis) bar rect should have its height changed as the data in the props passed down to it will have different values for that time period.

Question

Why might the bar chart not update itself automatically like we would expect in native d3 and how might I solve this issue?
3 Answers

React does not recognize the state change of arrays the way you do it.

Set data as a new array reference like this.

this.setState({data: [...data]});

This way, React can detect changes in array states by recognizing them as a new reference of array.

Edit:

Also, you cannot use react hook inside of a class component. The article you provided shows react hook so if you were using the same code for BarChart component, it would not work.

The dependencies in the example of useD3 from your reference are not right. Because the data.length will not change so the hook useD3 will not re-run the function used to draw the chart.

It should be:

useD3(() => {}, [data])

The issue is how you're updating data in handleAUM. When you change an attribute of an object in your data array directly, React doesn't detect a change in that item and so none of the components that depend on data re-render themselves.

One way to trigger a change is by replacing data with a new array. For example, you can use map to get an updated data array (I used the hard-coded 4th index in the ternary to match your example):

this.setState((state) => ({ data: state.data.map(
    (d, idx) => idx === 4 ? {...d, aum: e.target.ariaValueNow } : d
)}));

This way, React sees a new object for data in the state and re-renders the relevant components.

Here's another example that uses the data from the original tutorial you posted: https://codepen.io/kaveh/pen/rNmjjJO

This one uses filter on data to update the state. Note that data itself never changes and on each update, a new array from it is being set in the state.

Related