If you are a react developer, you must have encountered useEffect hook more often than not. As per official React documentation:
useEffect is a React Hook that lets you synchronise a component with an external system.
Typical usage of useEffect:
Let’s look at an example of when developers, sometimes even senior developers, tend to use useEffect. (Warm up!!)
Fetch from useEffect
Now many of you might have run into a problem. If not, this could be a surprise for you. React 18 runs effects twice! So when you run the code above it will print ‘Do something!!’ twice! The component is mounted, then unmounted, and then remounted. Now if we add an api call here, it will be done twice!
As per react doc:
When Strict Mode is on, in development, React runs setup and cleanup one extra time before the actual setup.
This is mainly to ensure problems appear before hand. Essentially, react is telling you that you are using the effect wrong! This isn’t what you use useEffect for. It is for ‘synchronisation’! Refer to the definition mentioned at the top.
Also, remember in production, the effect will run only once.
It is advised not to do fetch from useEffect. A Few possible reasons:
> You start fetching too late, which is inefficient.
> You don’t have a good place to cache the result between components.
> There is no de-duplication between requests.
Use libraries like React Query or similar.
Fire and Forget effects vs Synchronise effects
Lets look at what React doc says:
Effects are an escape hatch from the React paradigm. They let you “step outside” of React and synchronise your components with some external system like a non-React widget, network, or the browser DOM. If there is no external system involved (for example, if you want to update a component’s state when some props or state change), you shouldn’t need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.
So, unless you don’t need to talk to some external system (example subscribe/unsubscribe) you don’t really need the useEffect.
In the above example, because we are looking at some external system to subscribe to the items list, we can do it any number of times. It doesn’t matter. We just want to synchronise when the list changes. Since its external, the item list is going to be the same. If it changes, useEffect helps us synchronise.
Fire and forget effects examples(which we don’t want to add in useEffect):
- Analytics call as part of some action
- sending a data asynchronously but not awaiting a response.
Synchronise effects are those where you are talking to an external system. Since this is an external system, you would expect the same behaviour. And so it is fine to subscribe and un-subscribe.
Where do these fire and forget events go?
Outside the rendering!! Event handlers
Consider this, if a button click and so the event handler causes a state change, it causes the component to re-render. Also, remember that react can remount the component. So the effect might be executing multiple times.
Keep it in event handlers! Outside the useEffect. That way it executes only once.Example
As we can see above, when an event happens, we can handle the effect right there in event handler. If we rely on state change, there will be useEffect listening to this change and then trigger the effect.
If there are multiple useEffects, this event can trigger a chain state changes which might trigger other effects and so multiple renders.
This is hard to visualise!!
You don’t need Effects to transform data for rendering
Consider the following example:
In the above example, useEffect has been added to calculate the total. Do we really need it? Instead we can directly calculate as below:
If the computation is going to be heavy, use useMemo(). A thumb rule, if you see setState inside a useEffect, it should be warning sign for you! When something can be calculated from the existing props or state, don’t put it in state. Instead, calculate it during rendering. This makes your code faster (you avoid the extra “cascading” updates), simpler (you remove some code), and less error-prone (you avoid bugs caused by different state variables getting out of sync with each other).
Resetting all state when a prop changes
As we can see, this is inefficient. The component first renders with a stale value of comment. It is also complicated because you’d need to do this in every component that has some state inside it. For example, if the comment UI is nested, you’d want to clear out nested comment state too.
Solution: Give a unique key!
Adjusting some state when a prop changes
There are times when state change, a reset or partial update, might be required based on prop change.
In the example above, selection is updated based on items prop. You want to update it to null whenever items array is different.
In this case as well, Items and child components will render with stale selection first. React updates DOM and runs the effect. setSelection causes another re-render starting the whole process again.
Instead adjust the state while rendering.
While this may be hard to understand but ensures that the component doesn’t render with the stale value.
A better approach: Check if you can reset the state using a key or calculate everything during rendering itself.
Now no state adjustment is required. If selected item id is there in the list, it remains selected else selection becomes null.
Sharing logic between event handlers
You should avoid event specific logic inside an effect. Because you don’t need to.
In the above example, you want to update the list when you click on update button and console log the same. Adding the console.log call to both buttons’ click handlers feels repetitive so you might be tempted to place this logic in the Effect.
This causes a bug! On page refresh, if app ‘remembers’ the state on page reload, the item.isInList will still be true. And so, the console.log gets called on every page refresh. Imagine if we were showing a notification.
As stated in React docs:
When you’re not sure whether some code should be in an Effect or in an event handler, ask yourself why this code needs to run. Use Effects only for code that should run because the component was displayed to the user.
In the example above, the console.log should happen because the user pressed the button, not because the page was displayed! Delete the Effect and put the shared logic into a function that you call from both event handlers.
Another example of the same could be of an analytics call!
If there is an analytics event(for example, mix-panel event) you want to fire when the page loads, it should be part of an effect. This is because the reason to send the analytics event is that the page was displayed.
But, you want to fire another mixpanel event based on if a button was clicked, you should avoid doing it in effect. You might be tempted to use an useEffect based on the state change, after the button was clicked. Because, you don’t want to fire this event because page was loaded. Instead, you want to send it at that specific time when the button was clicked. Fire this event directly from the event handler!
In a nutshell:
- Any calculation which is derived and can be done during a page render should not be part of an effect.
- Don’t use effects for caching expensive calculations. Keep it as part of page render. Rather use useMemo.
- If you need to reset the state of a component or entire component tree make use of key.
- To reset a particular bit of state in response to a prop change, set it during rendering.
- Golden rule: If you see a code which needs to run because page was displayed, should be part of the effect. Rest can go to events.
- Updating state for multiple components is efficient when done in single event.
- If you need to keep the state in sync for different component, list the state up.
- If you are doing fetch in an effect, ensure you do the clean up to avoid race conditions.
Note: Are you planning to move to React 18. Start with the new docs (still in beta): https://beta.reactjs.org/
All the code snippet images have been created using https://codesnap.dev/
Using useEffect!! was originally published in Walmart Global Tech Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.
Article Link: Using useEffect!!. If you are a react developer, you must… | by Manish Singh | Walmart Global Tech Blog | Mar, 2023 | Medium