Reputation: 2136
I have made a working example here: https://codesandbox.io/s/magical-flower-o0gyn?file=/src/App.js
When I click the hide button I want to save the updated data to localstorage:
setValueWithCallback
runs, sets the callback to a ref & sets the stateuseEffect
kicks in, calls the callback with the updated datasaveToLocalStorage
is called, in a useCallback
with data
set as a dependencyThe problem is at the 3rd step, what gets saved to localstorage is {visible: true}
for both.
I know if I change this line:
const saveToLocalStorage = useCallback(() => {
localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
}, [data]);
To this:
const saveToLocalStorage = localStorage.setItem(
`_colProps`,
JSON.stringify(data.map((e) => ({ id: e.id, visible: e.visible })))
);
It works, but I cannot get my head around, why it does not with the first. I assume it must be some closure thing, but I don't see it.
If data
has already been updated, and useEffect
ran the callback, why it is not updated in the dependencies array?.
Yes, the example is weird and the 2nd solution is perfectly fine, I just wanted to demonstrate the problem.
Thanks for the help!
Upvotes: 3
Views: 8737
Reputation: 29282
Problem in your code is because of a closure of saveToLocalStorage
function over the local state of the component.
Inside the setValueWithCallback
function, you save the reference to the saveToLocalStorage
function using the callback
parameter that is passed to setValueWithCallback
function and this is where your problem is.
useCallback
hook will update the function reference of saveToLocalStorage
function but you do not call that updated function. Instead, you call the function you saved in callbackRef.current
which is not the updated function, but an old one which has a closure over the state with value of visible
property in both objects set to true
.
You can solve this problem by passing in the data
as an argument to the callback function. You already pass the argument when you call callbackRef.current(data)
but you don't make use of it inside the saveToLocalStorage
function.
Change the saveToLocalStorage
to make use of the argument that is passed from inside of the useEffect
hook.
const saveToLocalStorage = useCallback((updatedData) => {
localStorage.setItem(
`_colProps`,
JSON.stringify(updatedData.map((e) => ({ id: e.id, visible: e.visible })))
);
}, []);
Another way to solve this problem is to get rid of callbackRef
and just call the saveToLocalStorage
function from inside of the useEffect
hook.
useEffect(() => {
saveToLocalStorage();
}, [data, saveToLocalStorage]);
Upvotes: 5
Reputation: 667
Your data
is of array type and react only do shallow comparison
when an state update occur, so basically it will check for data
reference change not for its value change. That is why, your saveToLocalStorage not re-initiated when data's
value changes.
Upvotes: 0