I'm trying to learn about react's useEffect cleanup.
I came across this example on how to use cleanup.
(reference: https://blog.logrocket.com/understanding-react-useeffect-cleanup-function/).
const [data, setData] = useState({})
useEffect(() => {
// set our variable to true
let isApiSubscribed = true;
axios.get(API).then((res) => {
if (isApiSubscribed) {
// handle success
setData(res.data)
}
});
return () => {
// cancel the subscription
isApiSubscribed = false;
};
}, []);
But would like to ask, is it possible for the promise to be resolved in between the time when the component unmounts and cleanup is called, causing an error, that is, accessing the state that has been deallocated?
While I can't precisely tell you when useEffect's unsubscribe function is called more specifically than "when the component is unmounted", I can answer your question about the implications of when it is called:
is it possible for the promise to be resolved in between the time when the component unmounts and cleanup is called, causing an error, that is, accessing the state that has been deallocated?
In the code as written, it is possible for the Promise to resolve after the component unmounts. Your isApiSubscribed flag is a way of hiding the warning that might be displayed when this occurs in development mode, but it's not the correct way of dealing with this scenario.
When this happens when react is running in development mode, you might get a warning message like this:
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 the componentWillUnmount method.
The point of this warning message is that you should take care to stop doing work that's no longer relevant after a particular component is unmounted. Not calling setData does prevent you from updating the state, but the work of the HTTP request will still always be completed, even if the component is unmounted. That's the bit that React is telling you that you should consider cancelling.
The "correct" way of altering this pattern to avoid spending unnecessary resources would be to stop the execution of the HTTP request when the component unmounts, which will cause the promise to reject:
useEffect(() => {
const abortController = new AbortController();
axios.get(API, { signal: abortController.signal }).then((res) => {
setData(res.data)
});
return () => {
abortController.abort();
};
}, []);
This ensures that not only do you not call setData on a component that's unmounted (which is a relatively trivial amount of work), but that you cancel the much larger piece of work which is a HTTP request (and associated response handling) when the component is unmounted.
It is theoretically possible that the following series of events could happen:
Task starts
Task completes
Promise resolves
Component unmounts
Next event tick triggers and the continuation passed to then() is called, calling setData(), triggering the warning message
But this requires precise timing and the spirit of the warning message is to prevent the much more common scenario:
Task starts
Component unmounts
Task completes
Promise resolves
Next event tick triggers
Related
Does a render happen before function in React Hooks useEffect is called?
useLayoutEffect which is executed synchronously after all DOM
mutations. Use this to read layout from the DOM and synchronously
re-render. Updates scheduled inside useLayoutEffect will be flushed
synchronously, before the browser has a chance to paint.
As far as I know useEffect is executed asynchronously after painting in the browser.
So why it does say that the
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
? I feel there are inconsistencies, or am I missing something?
https://github.com/facebook/react/issues/21400
useEffect is only synchronously run when an update is scheduled from
useLayoutEffects. Updates from within layout effects must be applied
synchronously (before paint) as that's the only point where users can
e.g. tweak tooltip positions, dialog sizes, etc without the user
seeing a flash of invalid content. In order to preserve the overall
contract about when effects will run, React must also synchronously
flush pending passive effects before handling the update.
useEffect(() => {
const load = async () => {
try {
setloading(true);
const data = (await axios.get("/api/rooms/getallrooms")).data;
setrooms(data);
setloading(false);
} catch (error) {
seterror(true);
console.log(error);
setloading(false);
}
};
load(); // Do this. The code is only for referencie
}, []);
function App() {
const [buttonClick, setButtonClick] = useState('No')
async function handleClick(){
setButtonClick('Yes')
const sleep = (ms) ={return new Promise(resolve => setTimeout(resolve, ms))}
await sleep(2000)
console.log('this ran after state was changed')
}
return(
<>
<button onclick={handleClick}>Click me</>
{buttonClick}
</>
)
}
How come if I click the button, which sets the state of buttonClick, does it not break out of the function? It still sleeps for 2 seconds then logs my message in the console.
My thinking is that since it causes a component re-render, it would lead me to believe that the function App is returning and would break out of the handleClick function.
My thoughts on why this might be is that it might be when React is compiled it is just storing the value of the return somewhere else, and not actually returning the broader function App().
This has more to do with how Javascript works rather than React, I'll do my best to explain:
When you click the button your handleClick function is invoked.
handleClick is now on the call stack and will not terminate until either a value is returned or an error is thrown.
The first instruction in handleClick is to update the state, however, updating the state will not terminate the invokation of handleClick and even if the component were to unmount before it had a chance to finish, any call to setButtonClick on an unmounted component would result in what is known as a memory leak.
Furthermore the setTimeout and async nature of this function will end up in various parts of the event loop, but that has little to do with the original question.
You can learn more about the nature of Javascript functions here.
How can I await, to resolve/reject, the promise returned by a function wrapped in the react hook "useMemo"?
Currently, my code looks like this:
// Get the persisted email/username
const persistedUsername = useMemo(async () => {
let username;
try {
username = await AsyncStorage.getData(`#${ASYNC_STORAGE_KEY}:username`);
} catch {}
return username;
}, []);
EDIT
What I am trying to achieve is to get the data before the component is rendered, some kind of "componentWillMount" life-cycle. My two options were:
Computing the value using useMemo hook to avoid unnecessary recomputations.
Combine useEffect + useState. Not the best idea because useEffect runs after the component paints.
#DennisVash has proposed the solution to this problem in the comments:
Blocking all the visual effects using the useLayoutEffect hook (some kind of componentDidMount/componentDidUpdate) where the code runs immediately after the DOM has been updated, but before the browser has had a chance to "paint" those changes.
As you can see, persistedUsername is still a promise (I am not waiting the result of the asynchronous function)...
Any ideas? Is it not a good idea to perform asynchronous jobs with this hook? Any custom hook?
Also, what are the disadvantages of performing this operation in this way compared to using useEffect and useState?
Same thing with useEffect and useState:
useEffect(() => {
// Get the persisted email/username
(async () => {
const persistedUsername = await AsyncStorage.getData(
`#${ASYNC_STORAGE_KEY}:username`
);
emailOrUsernameInput.current.setText(persistedUsername);
})();
}, []);
Thank you.
Seems like the question is about how to use componentWillMount with hooks which is almost equivalent to useLayoutEffect (since componentWillMount is deprecated).
For more additional info, you should NOT resolve async action in useMemo since you will block the thread (and JS is single-threaded).
Meaning, you will wait until the promise will be resolved, and then it will continue with computing the component.
On other hand, async hooks like useEffect is a better option since it won't block it.
I am confused right now. Learning js frontend and how to make API requests.
Till now, whenever I made an API call, I always used something, that let me handle an asynchronous request (axios or thunk).
But now I got a case, where I am making a request to my Firebase/Firestore and in the yt video, that I watched, only useEffect was used. Without any async/await, like:
useEffect(() => {
// snapshot - to check, if there is a change (like a new entry) in the database
db.collection('products').onSnapshot((snapshot) => {
setProducts(snapshot.docs.map((doc) => doc.data()));
});
}, []);
Why is this?
In the example code snippet
useEffect(() => {
// snapshot - to check, if there is a change (like a new entry) in the database
db.collection("products").onSnapshot(snapshot => {
setProducts(snapshot.docs.map(doc => doc.data()))
})
}, []);
db.collection("products") manages its own asynchronous event handling. When a "snapshot" event is received it invokes the onSnapshot callback and updates state. There is nothing to await on, especially since there is no value returned.
You should also note that useEffect hook callbacks are 100% synchronous code, they can't be marked async and await any code calls. They can, however, call async functions that do their own awaiting and such.
About the questions in your title
Use a useEffect hook any time you want to issue any side-effect from the component lifecycle, like console logging state, make data fetches, dispatching actions. This is mostly due to the fact that the entire function body of a functional react component is to be considered a pure function. useEffect with dependencies is more akin to a class-based component's componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. Alone they are just called as part of the react component lifecycle.
Use axios, or fetch, or any other data fetching code whenever you need to fetch that data.
useEffect() - It is a hook that allows us to perform side effects in functional components like network request etc.
Axios - It is just a library where we can make http request like fetch API in javascript and also axios is promise based.
I am having issue with state as i'm not 100% i'm using componentDidMount and componentWillMount correctly.
I have set the constructor and super props, and I am getting a user object with the getCurrentUser() method and setting the user state with the new object.
componentWillMount() {
const current_user = getCurrentUser().then(user => {
this.setState({
user: user
});
console.log(user);
});
}
componentDidMount() {
console.log(this.state.user);
}
It logs the user correctly in componentWillMount, but logs an empty object in componentDidMount.
Any guidance would be massively appreciated!
Simply don't use componentWillMount,
Do it in componentDidMount.
In practice, componentDidMount is the best place to put calls to fetch data, for two reasons:
Using DidMount makes it clear that data won’t be loaded until after the initial render. This reminds you to set up initial state properly, so you don’t end up with undefined state that causes errors.
If you ever need to render your app on the server (SSR/isomorphic/other buzzwords), componentWillMount will actually be called twice – once on the server, and again on the client – which is probably not what you want. Putting the data loading code in componentDidMount will ensure that data is only fetched from the client.
getCurrentUser is an asynchronous method which calls another asynchronous method (setState).
I am pretty sure that you will first see the log entry from componentDidMount and only afterwards the log entry from componentWillMount.
What's happening is:
React calls componentWillMount
You start an async call (getCurrentUser)
componentWillMount returns immediatelly, without waiting for the promise to complete
React calls componentDidMount
Your promise resolves
The logs are due to the asynchronous nature of your method getCurrentUser. When you call getCurrentUser in componentWillMount, it might result in an output after the componentDidMount has finished executing and hence you see the initial state in componentDidMount. However the console.log in componentWillMount is in the getCurrentUser .then promise callback which will log the current value received from getCurrentUser()