Reputation: 123
experts, please explain me, why in the following code the state of the property won't be cleaned in the useEffect cleanup function?
My component:
export default function TestComp() {
let { id } = useParams();
const [value, setValue] = useState(null);
console.log('[TestComp] called...');
const cleanup = () => {
console.log('[TestComp] old value', value);
setValue(null);
};
useEffect(() => {
console.log('[TestComp] id changed: ', id);
console.log('[TestComp] value in useEffect', value);
setValue(id);
return () => {
cleanup();
}
}, [id]);
return (<React.Fragment>Test id: {id}</React.Fragment>)
}
Console output:
[TestComp] called... TestComp.js:8
[TestComp] old value satellites:MTP TestComp.js:11
[TestComp] id changed: satellites:MTP TestComp.js:16
[TestComp] value in useEffect satellites:FPGA TestComp.js:17
[TestComp] called... 2 TestComp.js:8
[TestComp] old value satellites:FPGA TestComp.js:11
[TestComp] id changed: satellites:FNE TestComp.js:16
[TestComp] value in useEffect satellites:MTP TestComp.js:17
[TestComp] called... TestComp.js:8
I expect that, when useEffect will be called for the 2. time, the value wull be cleaned up and is null, but it still keeps the old value:
value in useEffect satellites:MTP TestComp.js:17
Thank you in advance.
Upvotes: 3
Views: 3982
Reputation: 467
Return function from useEffect just cleans up the previous effects before applying the next effects. But main problem in your code is
const cleanup = () => {
console.log('[TestComp] old value', value);
setValue(null); // This is not prefer way to use setValue here.
}
Normally, During cleans up we unsubscribe from external service/subscription but here you are changing the state which make no sense here and immediately getting update by useEffect setValue that run and just after setValue inside cleanup called which is also reason of calling effect again,
Check your code after adding setTimeout in your useEffect.
`useEffect(() => {
console.log('[TestComp] id changed: ', id);
console.log('[TestComp]:Effect value in useEffect', value);
setValue(id);
return () => {
setTimeout(()=> cleanup(), 5000)
}
}, [id]);`
Possible Solution -
In above case you are using id, lift up this property to parent component and pass it as property to TestComp component.
when effect runs whole component get re-render and scope get destroyed but all state maintain inside the closure of useState hooks.
working example for above situation
Upvotes: 1
Reputation: 53884
You might want to add another useEffect
, because, in the current situation, the cleanup
function will run only on unmount which is pretty useless for the current logic.
export default function TestComp() {
let { id } = useParams();
const [value, setValue] = useState(null);
console.log('[TestComp] called...');
useEffect(() => {
console.log('[TestComp] id changed: ', id);
console.log('[TestComp] value in useEffect', value);
setValue(id);
/* v Useless cleanup because the component unmounts anyway,
the `value` state will be cleaned automatically.
return () => {
cleanup();
}
*/
}, [id]);
// ^ Firstly, the `value` changed on id change
// v Secondly, the `value` will be reset on `value` change
useEffect(() => {
console.log('[TestComp] old value', value);
setValue(null);
}, [value]);
return <>Test id: {id}</>;
}
Upvotes: 1
Reputation: 326
I think it's a matter of the sequence in which things happen.
useEffect
will 'capture' value as part of a closure when it is called, i.e. when the TestComp
function is called. When id changes React will schedule a call to the cleanup function and then a call to the effect. However TestComp
is not called again until after the new effect function has been called.
You can see this in the log as each old value
from the cleanup function is immediately followed by id changed
from the effect function.
And because the new effect functions 'captures' value from when TestComp
was called, it will not see the value set by the cleanup function.
Also note, at least from testing I saw React complain about value
and cleanup
not being in the dependencies for useEffect
.
Upvotes: 0