When using react Strict Mode useEffect is always invoked at least twice, i.e.
useEffect(() => {
console.log('Hello, World!');
}, []);
The above code will print "Hello, World!" "Hello, World!".
This breaks how I have traditionally implemented side-effects, e.g. log out upon visiting /log-out page:
export const LogOutPage = () => {
const { logout } = useAuth();
useEffect(() => {
logout({
onCompleted: () => {
console.log('logged out');
},
variables: {},
});
}, []);
return null;
};
useEffect will be invoked twice and so is onCompleted, causing "logged out" to be logged twice.
I thought this could be solved by using state to prevent useEffect from being called the second time…
export const LogOutPage = () => {
const [loggedOut, setLoggedOut] = useState(false);
const { logout } = useAuth();
useEffect(() => {
if (loggedOut) {
return;
}
console.log('loggedOut: ', loggedOut);
setLoggedOut(true);
logout({
onCompleted: () => {
console.log('logged out');
},
variables: {},
});
}, [loggedOut]);
return null;
};
However, because the initial value of loggedOut is false, it still executes that code path twice, i.e. it will wring "loggedOut: false" "loggedOut: false"
It would appear that the only way to prevent this is to trigger the desired side-effect by a change in state, i.e.
export const LogOutPage = () => {
const [startedLogOut, setStartedLogOut] = useState(false);
const [loggedOut, setLoggedOut] = useState(false);
const { logout } = useAuth();
useEffect(() => {
// This will be invoked twice.
// However, the operation is idempotent.
setStartedLogOut(true);
}, []);
useEffect(() => {
if (!startedLogOut) {
return;
}
setLoggedOut(true);
if (loggedOut) {
return;
}
logout({
onCompleted: () => {
console.log('logged out');
},
variables: {},
});
}, [startedLogOut, loggedOut]);
return null;
};
This indeed works, however, it feels convoluted, and it is not an pattern I’ve observed in the wild. Asking this question to confirm that this is how I should be implementing side-effects when using Strict Mode.
>Solution :
Those effects which should be run only once can be handled using the ref.
export default const LogOutPage = () => {
const [loggedOut, setLoggedOut] = useRef(false);
const { logout } = useAuth();
useEffect(() => {
if (loggedOut.current) {
return;
}
console.log('loggedOut: ', loggedOut.current);
loggedOut.current = true;
logout({
onCompleted: () => {
console.log('logged out');
},
variables: {},
});
}, []);
return null;
};
More reading.