When React developers discuss useEffect on social media, it goes with a lot of negativity, creating the impression that the hook is a buggy thing in React. However, the real reason for the problems with useEffect is that it should not be used when there is no need for it, and its valid use cases are well described and known. It's true that the useEffect hook is easy to misuse by less experienced developers. They may overuse in situations where it's not appropriate and then struggle to figure out what went wrong. However, experienced React developers don't see this as a flaw in useEffect: it's just a skill issue. The hook does exactly what it's supposed to do - just don't misuse it.
According to the React doc, Effects let you specify side effects that are caused by rendering. With Effects, you step out of your React code and synchronize with external systems like network, third-party widgets, browser APIs, and so on.
You shouldn't use an Effect, if you just want to update a component's state when some props or state change but there is no external system involved.
Below we look at two of these external systems in four custom React components: the network in SearchResults, ChatRoom and Form, and a third-party widget in Map.
Custom React component SearchResults
Tasks:
- when the component becomes visible on the screen
- fetch the search results from the network
- keep the results matching the current query and page while the component is displayed
Default React restrictions:
- fetching the results should happen no matter where the query came from
- however, fetching can't happen during rendering because it's not a calculation
- but there is no single event, like typing, that changes the query: it also changes from the URL or by navigating Back and Forward
What to specify in the useEffect in this case:
- setup code that fetches the results for the current query and page
- cleanup code that cancels the fetch or ignores its stale result
- the list of dependencies: the query and the page
How useEffect works in this case:
- The component is added to the page, or the query or page has changed, like the user types the next letter
- After the screen updates, React calls your setup function (if the query or page has changed, first the cleanup function, which marks the previous fetch as stale)
- Component is removed from the page, like the user navigates to another screen
- React calls your cleanup function
Stale results are responses that arrive after the user no longer needs them, for example, when they quickly change what they are typing. Without cleanup you would be displaying the wrong search results. With it, all responses except the last requested one are ignored.
What the React docs recommend instead of useEffect for fetching:
- if you use a modern React framework like Next.js, use its built-in data fetching mechanism
- otherwise, use a client-side cache like TanStack Query, useSWR, or React Router 6.4+. You can also build your own with logic to deduplicate requests, cache responses, and avoid network waterfalls
- If you still want to fetch data in Effects, extract your fetching logic into a custom Hook to decrease the number of raw useEffect calls in your components and this way improve maintainability of your application
Custom React component ChatRoom
Tasks:
- when the component becomes visible on the screen
- connect it to the chat server
- keep it connected while it is displayed to the user
Default React restrictions:
- setting up a server connection should happen no matter which interaction caused the component to appear
- however, connecting to a server can't happen during rendering because it's not a calculation
- but there is no single particular event, like a click, that causes the component to be displayed
What to specify in the useEffect in this case:
- setup code that connects to the chat server
- cleanup code that disconnects from the chat server
- the list of dependencies
How useEffect works in this case:
- The component is added to the page, or its props and state have changed, like the user picks a different chat room
- After the screen updates, React calls your setup function (if props and state have changed, first the cleanup function with the old values)
- Component is removed from the page, like the user navigates to another screen, for example, to the Settings page
- React calls your cleanup function
Cleanup functions are very important and you must test that you connected them correctly. If you don't stop, undo, or clean up (disconnect, unsubscribe, cancel, ignore) whatever the setup function was doing, the connections would keep piling up, creating an easy-to-miss bug.
Custom React component Form
Tasks:
- when the component appears on the screen
- send an analytics event that the form was displayed
Default React restrictions:
- sending the analytics event should happen because the form was displayed to the user
- however, it is a POST request, not a calculation, so it can't happen during rendering
- but there is no particular interaction, like pressing a button, that causes the form to be displayed
What to specify in the useEffect in this case:
- setup code that sends the analytics event
- no cleanup code, because there is nothing to stop or undo once the event is sent
- the list of dependencies: empty, so it runs once when the form appears
How useEffect works in this case:
- The component is added to the page
- After the screen updates, React calls your setup function, which sends the analytics event
What NOT to put in the useEffect in this case:
- the code that sends the request when the user clicks Submit
- it runs because of that click, not because the form was displayed
- so it should be written in the event handler of the Submit button, not in the useEffect
Custom React component Map
Tasks:
- when the component appears on the screen
- add a UI map widget that isn't written in React, like Leaflet
- keep the widget's zoom level in sync with the zoom level in React while the component is displayed
Default React restrictions:
- the widget's zoom level should match the zoom level in React no matter what changed it
- however, the widget is a third-party library, it isn't controlled by React
- but the + and − buttons only change the zoom level in React, not the widget
What to specify in the useEffect in this case:
- setup code that calls the widget's setZoom method with the current zoom level
- no cleanup code is needed here: the widget doesn't start anything that keeps running, so there is nothing to stop when the component is removed.
- the list of dependencies: the zoom level
How useEffect works in this case:
- The component is added to the page, or the zoom level has changed, like the user clicks + or −
- After the screen updates, React calls your setup function
- Component is removed from the page
Never miss a post! Share it!