-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
useState is unsafe #14113
Comments
Handle it in useEffect by returning a function that cancels the set call on unmount? useEffect(() => {
let unmounted = false;
apiCall(n).then(newN => {
if (!unmounted) {
setN(newN);
}
});
return () => {
unmounted = true;
};
}); |
I have thought about the issue again. useEffect(() => {
let unmounted = false;
apiCall().then(result => {
if (!unmounted) {
setter(result);
}
}, []);
return () => unmounted = true;
}); If it depends on props, we should pass them as the second argument useEffect(() => {
let changedAfterOrUnmounted = false;
apiCall(prop).then(result => {
if (!changedAfterOrUnmounted) {
setter(result);
}
}, [prop]);
return () => changedAfterOrUnmounted = true;
}); This solution has an additional benefit. If props has been changed, api call with old props values result is not needed. So, I think it is not a bug :) |
Yep this works as intended. You could also abort the request in the effect cleanup (which was pretty annoying to do in a class). In practice Suspense is intended to be the primary data fetching mechanism. |
can I use async/await pattern in this case somehow?
|
Edit: this is probably better: #14113 (comment)Not like this, no. You can do this though. const isMounted = useRef(true);
useEffect(() => {
performCall();
return () => {
isMounted.current = false;
};
}, []);
async function performCall() {
let newN = await apiCall(n)
if (isMounted.current) {
setN(newN);
}
} Longer term, Suspense will be the recommended solution instead. |
Can you elaborate why Suspense is the recommended solution over using hooks? API wise I can imagine hooks being more flexible. |
I mean that Suspense is the intended API for data fetching because it would look something like function YourComponent() {
const data = YourAPIResource.read();
return <h1>hello, {data.name}</h1>;
} No need to handle effects, race conditions, etc, by yourself.
You can use Suspense inside of Hooks if you need for some reason, but for many use cases Suspense alone should be enough. |
Ah, I was wondering if Suspense could be used from within a hook. Looking forward to seeing that in action. Totally missed the |
The naming of |
It's the same concept though: const ref = useRef();
// ref.current is DOM node
return <div ref={ref} /> We're intentionally "widening" its meaning. It's also similar to what refs in OCaml mean. https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables |
Finally a easy solution to "cleanup" Thanks a lot @gaearon. (I'm using Gatsby, Suspense is not support yet) :( |
For what it's worth this is probably better because it can work for multiple fetches (e.g. if route params change): useEffect(() => {
let canceled = false;
async function performCall() {
let newN = await apiCall(n)
if (!canceled) {
setN(newN);
}
}
performCall();
return () => {
canceled = true;
};
}, []); |
How does this not violate the Rules of Hooks? |
@alecmerdler which rule? |
useState
return "unsafe" setter. If a component has been unmounted and the setter is called we will get a warning. In a class component we can handle unmount event and avoid this situation.Example: https://codesandbox.io/s/vmm13qmw67
Click the button and get a warning in the console.
Cleanup logic will be very complicated in this case. Because we should separate unmount and "after render" cleanup.
The text was updated successfully, but these errors were encountered: