domains / / vercel-labs/agent-skills
Effect cleanup and subscriptions
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.
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.