Home > database >  React setState hook not updating dependent element if passed a variable as opposed to explicit text
React setState hook not updating dependent element if passed a variable as opposed to explicit text

Time:04-16

I'm right on the verge of tossing React and just using vanilla JS but thought I'd check here first. I'm simply trying to pass the contents of a variable, which contains an object, into state and have that update the element that depends upon it. If I pass setState a variable containing the object, it doesn't work. If I pass it the explicit text of the object it does.

Using React v 18.0.0

    function buttonHandler(e) {
        e.preventDefault()
        let tmpObject = {...chartData}
        tmpObject.datasets[0].data = quoteData.map(entry => entry['3'])
        tmpObject.datasets[1].data = quoteData.map(({fastEma}) => fastEma)
        tmpObject.datasets[2].data = quoteData.map(({slowEma}) => slowEma)
        tmpObject.labels = quoteData.map(entry => new Date(entry.timestamp).toLocaleTimeString())
        console.log("from button:", tmpObject)
        setChartData(prevState => {
            console.log("tmpObject",tmpObject)
            return tmpObject
        })

    return <div>
        <button onClick={buttonHandler}>Update</button>

        <Line options={chartOptions} data={chartData}/>
    </div>

When I run the above, the output of the console.log is exactly as it should be but the element does not update. If I copy the object output from the console and paste is explicitly into the code it does work.

    function buttonHandler(e) {
        e.preventDefault()
        setChartData({...}) 

I've tried every imaginable variations on the below statement to no avail...

return {...prevState, ...tmpObject}

I'd greatly appreciate any suggestions.

EDIT: As another test I added the following html element to see if it got updated. It gets updated and shows the expected data. Still, I'm having a hard time understanding why the chart will update if I pass it explicit text but will not if I pass it a variable.

<p>{`${new Date().toLocaleTimeString()} {JSON.stringify(chartData)}`</p>

CodePudding user response:

The issue is that of state mutation. Even though you've shallow copied the chartData state you should keep in mind that this is a copy by reference. Each property is still a reference back into the original chartData object.

function buttonHandler(e) {
  e.preventDefault();

  let tmpObject = { ...chartData }; // <-- shallow copy ok
  tmpObject.datasets[0].data = quoteData.map(entry => entry['3']); // <-- mutation!!
  tmpObject.datasets[1].data = quoteData.map(({ fastEma }) => fastEma); // <-- mutation!!
  tmpObject.datasets[2].data = quoteData.map(({ slowEma }) => slowEma); // <-- mutation!!
  tmpObject.labels = quoteData.map(
    entry => new Date(entry.timestamp).toLocaleTimeString()
  );

  console.log("from button:", tmpObject);

  setChartData(prevState => {
    console.log("tmpObject",tmpObject);
    return tmpObject;
  });
}

In React not only does the next state need to be a new object reference, but so does any nested state that is being update.

See Immutable Update Pattern - It's a Redux doc but really explains why using mutable updates is key in React.

function buttonHandler(e) {
  e.preventDefault();

  setChartData(chartData => {
    const newChartData = {
      ...chartData,  // <-- shallow copy previous state
      labels: quoteData.map(
        entry => new Date(entry.timestamp).toLocaleTimeString()
      ),
      datasets: chartData.datasets.slice(),  // <-- new datasets array
    };

    newChartData.datasets[0] = {
      ...newChartData.datasets[0],                   // <-- shallow copy
      data: quoteData.map(entry => entry['3']),      // <-- then update
    };
    newChartData.datasets[1] = {
      ...newChartData.datasets[1],                   // <-- shallow copy
      data: quoteData.map(({ fastEma }) => fastEma), // <-- then update
    };
    newChartData.datasets[2] = {
      newChartData.datasets[2],                      // <-- shallow copy
      data: quoteData.map(({ slowEma }) => slowEma), // <-- then update
    };

    return newChartData;
  });
}

Check your work with an useEffect hook with a dependency on the chartData state:

useEffect(() => {
  console.log({ chartData });
}, [chartData]);

If there's still updating issue then check the code of the Line component to see if it's doing any sort of mounting memoization of the passed data prop.

  • Related