A fetch started in an effect keeps running after unmount, and its then sets state on a component that no longer exists.
useEffect(() => {
const controller = new AbortController()
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(e => {
if (e.name !== 'AbortError') throw e
})
return () => controller.abort()
}, [url])
The abort rejects the promise with an AbortError, which has to be swallowed explicitly or it becomes an unhandled rejection in every navigation. Cancelling also fixes the race rather than only the warning: two fetches for two values of url can resolve out of order, and whichever lands last wins regardless of which was requested last. The abort makes that impossible instead of merely unlikely.