DEV Community

Stephane Rangaya
Stephane Rangaya

Posted on

How to make sure useEffect catches array changes

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
Enter fullscreen mode Exit fullscreen mode

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;
  }
Enter fullscreen mode Exit fullscreen mode

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;
  }
Enter fullscreen mode Exit fullscreen mode

Discussion (9)

Collapse
tobiasjacob profile image
Tobias Jacob

I don't think this is a good example. The fault is not, that react cannot look for array changes, but instead that you mutated your state.

NEVER MUTATE THE STATE

In the example above, this means, that you have to create a whole new array with whole new objects for react to pick up the changes. For example

const [data, setData] = useState([])

In another hook:
function capitalizeAllRows() {
setData(data.map((row) => ({...row, name: Capitalize(row.name)})))
}

note that with the spread operator, we created a new object.

I still like the idea of using hashs (for example json.stringify, even tough there are more performant ways) for applying effects if an array changes. Just the example is not well chosen

Collapse
mvozaar profile image
mvozaar

example is good but not for solving the problem. but for detection and verification of root cause is good :). it saves your time if you want to find problem. right solution for state changes is as you mentioned "NEVER MUTATE THE STATE".

Collapse
iquirino profile image
Igor Quirino

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

Collapse
stephane profile image
Stephane Rangaya Author

How would you do this?

Collapse
nicklaswinger profile image
Nicklas Pouey-Winger • Edited

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

Thread Thread
iquirino profile image
Igor Quirino
Collapse
harshan89 profile image
Harshan Morawaka

just use data.length as the dependency

Collapse
martin2844 profile image
martin2844

Thanks man. Could'nt figure out why react wasnt catching changes in my array

Collapse
mvozaar profile image
mvozaar

I have spotted similar behavior, when i missed object changes. changes was done by delete_by_key. this is "mutating the state".

in original code was used spread operator to clone new object, but in removal was used delete. additions was detected, but removal not.