useEffect

All about useEffect hook in React


The most misconcepted hook in React. Even senior developers make mistakes by using this effect. But let's understand why.

LinkIconHistory

Before React v16.8 (2019), components were class components. They had something called "componentDidMount", "componentDidUpdate", and "componentWillUnMount" where you could run some code depending on the component life cycle.

The API changed completely in React v16.8, when hooks where introduced, and useEffect was introduced to replace the 3 prior API from class components.

useEffect(() => {
  // componentDidMount?
}, [])
 
useEffect(() => {
  // componentDidUpdate?
}, [something, another])
 
useEffect(() => {
  return () => {
    // componentWillUnMount?
  }
}, [])

LinkIconWhat is useEffect?

You may intuitely assume useEffect is a lifecycle hook, where you can run code depending on the life cycle of the component, but is NOT.

While you could use useEffect that way, that's not its intended purpose. As Dan Abramov—mantainer of React at that time—explained:

"The mental model is synchronization. Not lifecycle".

useEfffect is meant to synchronize between your component and external source (e.g sync API response data with react).

When using useEffect, instead of asking "When does this effect run?", ask: "With which state or prop does this effect synchronize with?"

// with which state does this effect synchronize with?
useEffect(fn) // all state
useEffect(fn, [l) // no state
useEffect(fn, [these, states])

For example, let's synchronize a websocket that listen to a subscription and when the itemId changes, it will synchronize to another subscription. Of course, when the component unmounts it will unsubscribe.

useEffect(() => {
  // Synchronize with external system
  const sub = storeApi.subscribeToItem(itemId, setItemData) ;
  // Subscription disposal
  return sub.unsubscribe;
}, [itemid]); // Subscription dependency

The mental model is synchronization.

LinkIconDo not use useEffect:

  • to run code that derives from local source (props/state/conditions). Most of the times this code belongs outisde of useEffect or directly inside event handlers functions like onSubmit, onChange, or any handleFunction.
  • to run Action effects
  • to transform data
  • to fetch data. I know this is controversial but must use already built-in solutions like Tanstack Query or a framework.

LinkIconDo use useEffect:

  • to synchronize with external systems (APIs, subscriptions, DOM APIs, timers, local storage, web sockets).
  • to perform imperative DOM work that cannot be done during render (focus, measure, integrate third-party libs).
useEffect(() => {
  inputRef.current?.focus();
}, []); // runs after first paint
  • to run effects. What kind of effects? Short answer: Synchronized effects. There are two types of effects:
    • Action effects: Don't care about the result, such as console log, send anaylitics, async functions without waiting a response, code inside event handlers (side-effects), navigation, etc.
    • Synchronized/Activity effects: events that communicate with external systems and are long-lived.
  • to initialize/clean-up resources tied to the component lifecycle (add/remove event listeners, set/clear timers, open/close connections).

LinkIconUsage

useEffect(() => {
  // some code
}, []) // dependencies

LinkIconParameters

NameTypeDescription
effect() => void or () => () => voidThe effect logic to run after the component renders. Can optionally return a cleanup function, which runs before the effect re-runs or when the component unmounts.
deps (optional)any[]Array of dependencies that determine when the effect runs.
• Omit to run after every render (pitfall).
• Pass [] to run once after the first render.
• Pass [a, b] to run when either a or b changes.

LinkIconReturns

TypeDescription
voiduseEffect itself returns nothing.

LinkIconReference