Pitfall: using Effect instead of calculating during render
Don't use an Effect to adjust state based on other state. For example, don't use useEffect to set fullName based on firstName and lastName. Instead, calculate fullName = firstName + ' ' + lastName during rendering. This is simpler and more efficient.
React linter checks Effect dependencies are complete
React provides a linter rule to verify that you've specified your Effect's dependencies correctly. The linter will flag missing dependencies in the dependency array.
useEffectEvent to separate non-reactive code from Effects
Use useEffectEvent to wrap code inside an Effect Event. Code inside Effect Events isn't reactive, so it doesn't cause the Effect to re-run when it changes. This allows you to read the latest value of a prop or state without re-triggering the Effect.
Effect dependency list describes all reactive values used by the Effect
Think of the dependency list as a list of all the reactive values used by your Effect's code. You don't intentionally choose what to put on that list. The list describes your code. To change the dependency list, change the code.
Don't use Effects to transform data for rendering
You don't need an Effect to transform data for rendering. Instead, calculate as much as you can while rendering. Effects should only be used to synchronize with external systems.
Effects run and clean up once extra in development for validation
In development, React will immediately run and clean up your Effect one extra time. This ensures that you don't forget to implement the cleanup function. This is a development-only behavior.
Effect re-connects when dependency changes
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return <h1>Welcome to the {roomId} room!</h1>;
}
This example shows how an Effect re-synchronizes and re-connects when the roomId dependency changes.
Props are reactive values that cause Effects to re-synchronize
Props are reactive values, meaning they can change on a re-render. If an Effect depends on a prop, the Effect will re-synchronize whenever that prop changes.
useEffectEvent example to prevent unnecessary re-connection
import { useState, useEffect } from 'react';
import { useEffectEvent } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return <h1>Welcome to the {roomId} room!</h1>
}
This example shows how useEffectEvent lets the Effect depend only on roomId, while onConnected can still read the latest theme value without causing a reconnect.
Unnecessary Effect dependencies cause over-frequent re-runs or infinite loops
When writing an Effect, include every reactive value like props and state that the Effect reads in the dependency list. Unnecessary dependencies may cause your Effect to run too often, or even create an infinite loop.
Move object creation inside Effect to remove unnecessary dependency
If an Effect depends on an object created outside the Effect that changes on every render, move the object creation inside the Effect. This makes the Effect depend only on the primitive values it needs, reducing unnecessary re-runs.
Don't use Effects to handle user events
You don't need an Effect to handle user events. Event handlers exist specifically for this purpose and should be used instead of Effects for handling interactions.
Event handlers run only on the same interaction, Effects re-run on dependency changes
Event handlers only re-run when you perform the same interaction again. Effects re-synchronize if any of the reactive values they read, like props or state, are different than during the last render.
Effects synchronize components with external systems
Effects let you run code after rendering to synchronize your component with a system outside of React. Unlike event handlers which handle particular events, Effects are useful for operations like controlling non-React components, setting up server connections, or sending analytics logs when a component appears on screen.
Effect cleanup function example for disconnect
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, []);
return <h1>Welcome to the chat!</h1>;
}
This example shows how to set up a connection in an Effect and return a cleanup function that disconnects when the component unmounts.
useEffect for unavoidable side effects
If you have exhausted all other options and cannot find the right event handler for your side effect, you can attach it to your returned JSX with a useEffect call in your component. This tells React to execute it later, after rendering, when side effects are allowed. However, this approach should be your last resort.
Separate Effects for independent synchronization processes
When two parts of your component synchronize with different data sources or dependencies, use separate Effects instead of combining them into one. For example, one Effect can synchronize a select box to a list of planets, and another Effect can synchronize a different select box to places for the currently selected planet. Combining them would force unnecessary refetches when only one dependency changes.
Guard Effect execution with null or empty checks
When an Effect depends on state that might be empty (like a planetId that is initially ''), add a guard at the start of the Effect to return early if the required state hasn't been set yet. This prevents unnecessary API calls before the user has made a selection.
Move non-reactive values outside the component or inside the Effect
To make a value non-reactive so it doesn't need to be a dependency, you can either move it outside the component or move it inside the Effect body. This 'proves' to the linter that the value is not reactive.
Never suppress the exhaustive-deps linter without fixing the code
Suppressing the react-hooks/exhaustive-deps linter rule with a comment like eslint-disable-next-line should be avoided. It indicates a bug in the code. There is always a way to fix the code to follow the rules.
Multiple Effects with different dependency arrays
A component can have multiple useEffect hooks, each with different dependency arrays. The first Effect with an empty dependency array [] runs once on mount. A second Effect with [planetId] as dependencies runs whenever planetId changes.
Values calculated from props and state are reactive
Values that you calculate from props and state during rendering are also reactive. If the props or state change, your component re-renders and the calculated values also change. All variables from the component body used by the Effect should be in the dependency list.
Effects have a different lifecycle than components
Components mount, update, or unmount. Effects can only do two things: start synchronizing something, and later stop synchronizing it. This cycle can happen multiple times if the Effect depends on props and state that change over time.
Fixing infinite loops in Effects
If an Effect causes infinite loops, do not suppress the linter. Instead, check if the Effect represents an independent synchronization process. If it synchronizes multiple things, split it up. If you want to read a value without reacting to it, use Effect Events.
Separate unrelated logic into separate Effects
Each Effect should represent a separate and independent synchronization process. Do not add unrelated logic to an Effect only because it needs to run at the same time as another Effect. If you split up a cohesive piece of logic into separate Effects, it will be more difficult to maintain.
Linter rule checks reactive value dependencies
React provides a linter rule that checks whether every reactive value used by an Effect's code is declared as a dependency. This keeps the Effect synchronized to the latest props and state.
Dependencies array is required to specify what Effect depends on
You cannot choose your dependencies. Your dependencies must include every reactive value you read in the Effect. The linter enforces this rule.
React re-synchronizes Effects in development to verify cleanup works
In development, React forces each Effect to start and stop one extra time to verify that its cleanup is implemented correctly. This produces extra logs in development but does not happen in production.
Remove unnecessary dependencies from Effects
Avoid relying on objects and functions created during rendering as dependencies, because they will be different on every render and cause the Effect to re-synchronize every time. Read more about removing unnecessary dependencies.
Using ignore flag to handle stale async responses in Effects
When fetching data in an Effect, use a boolean flag to track if the component has unmounted or dependencies changed. Set ignore = false at the start of the Effect, then check it before updating state in the async response. In the cleanup function, set ignore = true to cancel pending state updates from stale requests.
Think about Effects independently from component lifecycle
Do not think about Effects in terms of component mounting, updating, or unmounting. Instead, think about each Effect independently from your component's lifecycle. An Effect describes how to synchronize an external system to the current props and state.
Stable values from useState and useRef are not reactive
The set function returned from useState and the ref object returned by useRef are stable and guaranteed to not change on re-render. Stable values are not reactive, so they may be omitted from dependencies, though including them is allowed.
Re-synchronization sequence when dependency changes
When a dependency changes, React will first call the cleanup function of the previous Effect with the old dependency values, then run the new Effect with the new dependency values.
Effect body specifies how to start synchronizing
The code inside the Effect's body specifies how to start synchronizing an external system with the current props and state.
Cleanup function specifies how to stop synchronizing
The cleanup function returned by an Effect specifies how to stop synchronizing. Some Effects don't return a cleanup function, and React will behave as if an empty cleanup function was returned.
Effects are not a tool for code reuse
Combining multiple Effects into one just to reduce repetitive code is not a good practice, because it forces unrelated synchronization to share dependencies. Instead, extract the repeated logic into a custom Hook, which properly encapsulates the synchronization pattern while keeping different parts independent.
Mutable values cannot be dependencies
Mutable values like location.pathname or ref.current cannot be dependencies. They are not reactive because changing them does not trigger a re-render. Reading mutable data during rendering breaks the purity of rendering.
Reactive values must be included in dependencies
Props, state, and other values declared inside the component are reactive because they are calculated during rendering and participate in the React data flow. Any reactive value that your Effect reads must be included in the dependency array.
Empty dependency array means Effect runs once on mount and cleanup on unmount
An empty dependency array [] means the Effect connects when the component mounts and disconnects when the component unmounts. No reactive values are used in the Effect.
Effects may need to synchronize multiple times while component is mounted
Sometimes it is necessary to start and stop synchronizing multiple times while the component remains mounted. This happens when dependent values change during the component's lifetime.
Dependencies are compared using Object.is
When comparing dependency array values between renders, React uses Object.is for comparison. If any value at the same position is different, React re-synchronizes the Effect.
Unstable dependency arrays cause issues
When unstable dependencies cause effects to fire too often or create infinite loops, this is a common breaking pattern with React Compiler. The compiler may memoize dependencies differently than manual approaches.
Effects relying on referential equality can break
When effects depend on objects or arrays maintaining the same reference across renders, the compiler may memoize differently than expected, causing effects to fire unexpectedly or create infinite loops.
Passing arguments to Effect Events
When an Effect Event reads a reactive value that should correspond to a specific event occurrence, pass that value as an argument to the Effect Event instead of reading it inside the Effect Event. For example, when logging page visits, pass the URL as an argument so the Effect Event sees the URL value at the time the Effect ran, not the latest URL value.
Example: Separating reactive and non-reactive logic with useEffectEvent
```js
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // theme removed from dependencies
return <h1>Welcome to the {roomId} room!</h1>
}
```
This example shows how useEffectEvent allows the notification to read the latest theme value without making the Effect re-run when theme changes. Only roomId changes trigger reconnection.
useEffectEvent hook purpose
The useEffectEvent hook extracts non-reactive logic out of Effects. It allows you to separate parts of your Effect's code that should not re-run when certain reactive values change. Code inside an Effect Event is not reactive and always sees the latest values of props and state, similar to event handler behavior.
Effect Events must not be dependencies
Effect Events are not reactive and must be omitted from an Effect's dependency array. Unlike regular functions or variables, Effect Events should never be listed as dependencies of useEffect. They should only be called from inside Effects.
Choosing between event handlers and Effects
Use event handlers for code that runs in response to specific user interactions like clicking a button. Use Effects for code that runs to keep the component synchronized with an external system, regardless of specific interactions. Consider why the code needs to run: if it's because of a specific interaction, use an event handler; if it's because something needs to stay in sync, use an Effect.
Effect Events limitations
Effect Events have two main limitations: first, only call them from inside Effects; second, never pass them to other components or Hooks. Effect Events should be declared directly next to the Effects that use them. Declaring and passing Effect Events to other functions or custom Hooks will cause problems.
Never suppress dependency linter for Effects
Avoid suppressing the exhaustive-deps linter rule with eslint-disable-next-line. Suppressing the linter causes React to stop warning you when your Effect needs to react to new reactive dependencies. This leads to bugs with stale values. Use useEffectEvent to fix these issues instead of suppressing the linter.
Effects are reactive
Logic inside Effects is reactive. If an Effect reads a reactive value such as a prop or state variable, you must specify it as a dependency. If a re-render causes that reactive value to change, React will re-run the Effect's logic with the new value. Effects re-synchronize whenever their dependencies change.
Effect dependency array controls re-runs
The second argument to useEffect is a dependency array that controls when the Effect re-runs. If omitted, the Effect runs after every render. An empty array [] means the Effect only runs once on mount. An array with dependencies means the Effect runs when any listed dependency changes, determined by Object.is comparison.
Cannot call DOM manipulation methods during rendering
You cannot call methods like play() or pause() on DOM elements during rendering because rendering must be pure and should only calculate JSX. Additionally, the DOM might not exist yet since React doesn't know what to create until the JSX is returned. Wrap such operations in useEffect.
Infinite loop pitfall with Effects and state
Setting state directly in an Effect without dependencies creates an infinite loop: the Effect runs, sets state, which triggers a re-render, which runs the Effect again. This is a common pitfall to avoid.
useEffect Hook import and basic syntax
Import useEffect from React with: import { useEffect } from 'react'. Call it at the top level of your component with a callback function. By default, the Effect runs after every render.
What Effects are for
Effects let you synchronize a component with external systems such as browser APIs, third-party widgets, network requests, or non-React code. Some components need to synchronize with external systems when they appear on screen.
Effects run after rendering completes
useEffect delays code execution until after the render is reflected on the screen. This means React updates the DOM first, then runs the Effect code. This is the key difference from running code during rendering.
Effect cleanup function pattern
Return a cleanup function from useEffect to perform cleanup before the Effect runs again or when the component unmounts. The cleanup function should undo whatever the Effect did. For example, addEventListener needs removeEventListener, subscribe needs unsubscribe, fetch needs abort or ignore logic.
Effects vs Events
Effects are caused by rendering itself, rather than by a particular event. Events are caused by specific user interactions (like button clicks). Effects run at the end of a commit after the screen updates, making them ideal for synchronizing React components with external systems.
Cleanup function timing
React calls the cleanup function before the Effect runs again and one final time when the component unmounts. This ensures proper cleanup of resources and prevents memory leaks.