loading...

How to make sure useEffect catches array changes

stephane profile image Stephane Rangaya ・1 min read

There is a way to use useEffect to only run when variables provided in a second argument are updated, like this:

const [count, setCount] = useState(0);

useEffect(() => {
  console.log('Something happened')
}, [count]); // Only re-run the effect if count changes

Unfortunately, this doesn't always work with arrays:

const [data, setData] = useState([
    {id: 1, name: 'Alexander'},
    {id: 2, name: 'Fabien'},
    {id: 3, name: 'Yuki'},
  ]);


useEffect(() => {
  console.log('Something happened')
}, [data]); // Changes won't be caught :( !

const onCapitalizeClick = () => {
  setData(capitalizeAllRows());
};

// in another hook:
function capitalizeAllRows() {
    setDataRows(props);
    dataRows.forEach(row => {
      row.name = Capitalize(row.name);
      setDataRows([...data]);
    });
    return dataRows;
  }

In this case, the effect won't run again because it is comparing the array at the highest level and it is therefore not picking up the change in capitalization of names: the array has the same amount of rows, nothing structurally changed.

But if you were to add a row, the changes will be picked up and the effect would be executed.

In order to have the effect always run when any changes happen to the array, stringifying the array works well!

Instead of using data in the second argument array of useEffect(), use [JSON.stringify(data)]:

const [data, setData] = useState([
    {id: 1, name: 'Alexander'},
    {id: 2, name: 'Fabien'},
    {id: 3, name: 'Yuki'},
  ]);


useEffect(() => {
  console.log('Something happened')
}, [JSON.stringify(data)]); // Changes will be caught :) !

const onCapitalizeClick = () => {
  setData(capitalizeAllRows());
};

// in another hook:
function capitalizeAllRows() {
    setDataRows(props);
    dataRows.forEach(row => {
      row.name = Capitalize(row.name);
      setDataRows([...data]);
    });
    return dataRows;
  }

Posted on by:

Discussion

markdown guide
 

JSON.stringify(data) will add extra processing...
Why not to just add a counter (setState(0)) and then increment it when object get changed?

 
 

I assume by calling setLoadCount((prevState) => prevState + 1) or something like that :)