Return a function from the effect; React calls it before the next run and when the component unmounts:
useEffect(() => {
const onResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', onResize)
return () => window.removeEventListener('resize', onResize)
}, [])
The handler has to be the same function in both calls, so define it once inside the effect. An inline arrow in removeEventListener removes nothing.
With an AbortController you can clean up several listeners at once:
useEffect(() => {
const controller = new AbortController()
const { signal } = controller
window.addEventListener('resize', onResize, { signal })
window.addEventListener('scroll', onScroll, { signal })
return () => controller.abort()
}, [])