UNPKG

@eoet/use-state-if-mounted

Version:

A hook for updating state only if the component is mounted.

97 lines (73 loc) 2.91 kB
# use-state-if-mounted It's inspired by https://github.com/NansD/use-state-if-mounted # @eoet/useStateIfMounted It's inspired by https://github.com/NansD/use-state-if-mounted A hook for updating state only if the component is mounted. Find it on [npm](https://github.com/eoet/use-state-if-mounted), or add it to your project : ```bash $ npm install @eoet/use-state-if-mounted # or $ yarn add @eoet/use-state-if-mounted ``` ---- ## 🔴 UPDATE This "solution" doesn't avoid leaks. Even AbortController doesn't seem to be the silver bullet against memory leaks 😰. Check out the [discussion in the comments](https://dev.to/nans/an-elegant-solution-for-memory-leaks-in-react-1hol)! ---- ## How to use Use this hook just like React's [useState](https://reactjs.org/docs/hooks-reference.html#usestate). This one hook only updates state if the component that called this hook is mounted. This allows us to avoid memory leaks and messages like this one : ``` Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. in Child (created by Holder) ``` ## Examples ### Basic usage ```javascript const [count, setCount] = useStateIfMounted(0); ``` ### "Real use case" usage Based from this [issue](https://github.com/facebook/react/issues/14113) from Github. ```javascript const apiCall = n => new Promise(resolve => setTimeout(() => resolve(n + 1), 3000)); const ShowApiCallResult = () => { const [n, setN] = useState(0); useEffect(() => { apiCall(n).then(newN => setN(newN)); }); return String(n); }; const RemoveComponentWithPendingApiCall = () => { const [show, setShow] = useState(true); return ( <React.Fragment> <button onClick={() => setShow(false)}>Click me</button> {show && <ShowApiCallResult />} </React.Fragment> ); }; ``` See [CodeSandbox](https://codesandbox.io/s/vmm13qmw67?file=/src/index.js). The issue can be fixed with our hook by simply replacing `useState` with `useStateIfMounted` : ```javascript const apiCall = n => new Promise(resolve => setTimeout(() => resolve(n + 1), 3000)); const ShowApiCallResult = () => { const [n, setN] = useStateIfMounted(0); // notice the change 🚀 useEffect(() => { apiCall(n).then(newN => setN(newN)); }); return String(n); }; const RemoveComponentWithPendingApiCall = () => { const [show, setShow] = useState(true); // this setShow will never cause a memory leak in this situation // so we can use vanilla setState return ( <React.Fragment> <button onClick={() => setShow(false)}>Click me</button> {show && <ShowApiCallResult />} </React.Fragment> ); }; ``` See [CodeSandbox](https://codesandbox.io/s/gracious-mahavira-3k62q?file=/src/index.js).