I have simple setup with outlet and dynamic routes and on route change I want to fetch new data.
import React, { useEffect, useState } from 'react';
import { Link, Outlet, useParams } from 'react-router-dom';
export default function OutletContainer() {
const [apiResponse, setApiResponse] = useState(null);
let { outletComponentId } = useParams();
useEffect(() => {
// keeps api response state from previous route.
console.log(' ue inv id', apiResponse, outletComponentId);
fetch(
`https://anapioficeandfire.com/api/characters/${outletComponentId}`
).then((r) => setApiResponse(r.url));
return () => {
console.log('runcleanup');
setApiResponse(null);
};
}, [outletComponentId]);
return (
<div style={{ display: 'flex' }}>
<nav style={{ borderRight: 'solid 1px', padding: '1rem' }}>
{console.log('api', apiResponse, outletComponentId)}
<Link key={13} to={`/outletContainer/${13}`}>
13
</Link>{' '}
<Link key={12} to={`/outletContainer/${12}`}>
12
</Link>{' '}
<Link key={2} to={`/outletContainer/${2}`}>
2
</Link>
</nav>
<Outlet />
</div>
);
}
So, the problem:
When i swtich from /outletContainer/${12}
to /outletContainer/${13}
console.log in useEffect keeps the apiResponse
value from route 12 in route 13, even thou cleanup method in useEffect set apiResponse
state null
return () => {
console.log('runcleanup');
setApiResponse(null);
};
My questions are:
1.Why is this happening? I was reading Dans blog post about useEffect but I got even more confused. Initial TLDR gave me hope, but down the road I got lost.
2.How to set state of a apiResponse
to null each time i switch route.
You can check whole code on a stackblitz
I think this is just an issue of misunderstanding Javascript closures. apiResponse
is missing from the useEffect
hook's dependency (for good reason since including it would create a render loop), so the code appears to be logging a stale state value.
useEffect(() => {
// keeps api response state from previous route.
console.log(' ue inv id', apiResponse, outletComponentId);
fetch(
`https://anapioficeandfire.com/api/characters/${outletComponentId}`
).then((r) => setApiResponse(r.url));
return () => {
console.log('runcleanup');
setApiResponse(null);
};
}, [outletComponentId]);
You are also console logging in the render return as an unintentional side-effect. Other than these I don't see any real issue here with the state updates.
I suggest the following for accurate value logging. Move the extraneous logs into a separate useEffect
hook to log when that value updates.
export default function OutletContainer() {
const [apiResponse, setApiResponse] = useState(null);
const { outletComponentId } = useParams();
useEffect(() => {
console.log('useEffect', { outletComponentId }); // <-- log when outletComponentId changes
if (outletComponentId) { // <-- only fetch, and update apiResponse, if outletComponentId
fetch(
`https://anapioficeandfire.com/api/characters/${outletComponentId}`
).then((r) => {
console.log("update apiResponse", r.url); // <-- enqueue state update
setApiResponse(r.url);
});
}
return () => {
console.log('run cleanup');
setApiResponse(null);
};
}, [outletComponentId]); // <-- outletComponentId is dependency
useEffect(() => {
console.log('api updated', apiResponse); // <-- log when apiResponse changes
}, [apiResponse]); // <-- apiResponse is dependency
useEffect(() => {
console.log("Rendered"); // <-- every render cycle
});
return (
<div style={{ display: 'flex' }}>
<nav style={{ borderRight: 'solid 1px', padding: '1rem' }}>
<Link key={13} to={`/outletContainer/${13}`}>
13
</Link>{' '}
<Link key={12} to={`/outletContainer/${12}`}>
12
</Link>{' '}
<Link key={2} to={`/outletContainer/${2}`}>
2
</Link>
</nav>
<Outlet />
</div>
);
}
Produced Logs:
When outlet 13 is clicked the cleanup function runs and enqueues an apiResponse
state update to null. Since the existing apiResponse
state value is already null the update is ignored. The useEffect
hook runs and updates the apiResponse
state and there's the log from the second useEffect
hook.
When outlet 12 is clicked the cleanup function runs and this time we see the update go through and log that the apiResponse
was set back to null. We also see the fetch made and update the apiResponse
to the new value.