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
JSON.stringify(data) will add extra processing...
Why not to just add a counter (setState(0)) and then increment it when object get changed?
How would you do this?
I assume by calling setLoadCount((prevState) => prevState + 1) or something like that :)
Maybe: sung.codes/blog/2018/11/08/emulate...
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
Thanks man. Could'nt figure out why react wasnt catching changes in my array