DEV Community

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;
  }

Discussion

pic
Editor guide
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

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

Thread Thread
iquirino profile image
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
martin2844 profile image
martin2844

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