Prompt
When using React hooks that interact with external systems (browser events, subscriptions, async requests), write code so it cannot leak resources or update state after the component unmounts.
Standard
- In
useEffect, always return a cleanup function that removes listeners/subscriptions. - For in-flight async work started in
useEffect(fetch, timers, requests), cancel/abort on cleanup (e.g.,AbortController) sosetStatecan’t run after unmount. - For event-based external data that multiple renders/components may depend on, prefer
useSyncExternalStoreover manual event subscription logic.
Example (safe effect + abort)
function Notifications() {
const [data, setData] = useState<Notification[]>([])
useEffect(() => {
const controller = new AbortController()
const onResize = () => {
// listener logic
}
window.addEventListener('resize', onResize)
fetch('/api/notifications', { signal: controller.signal })
.then(r => r.json())
.then(next => setData(next))
.catch(() => {
/* ignore abort */
})
return () => {
window.removeEventListener('resize', onResize)
controller.abort()
}
}, [])
return <div />
}
Example (subscription preference)
- If you’re maintaining state from a browser event source (e.g.,
resize) via listeners, consider modeling that external value withuseSyncExternalStorerather than ad-hocaddEventListener+setStatepatterns.