DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for useEffect firing twice in React 18
Shivam Jha
Shivam Jha

Posted on • Updated on • Originally published at shivamjha.io

useEffect firing twice in React 18

Gist

Acoording to React 18 Changelog:

In the future, React will provide a feature that lets components preserve state between unmounts. To prepare for it, React 18 introduces a new development-only check to Strict Mode. React will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount. If this breaks your app, consider removing Strict Mode until you can fix the components to be resilient to remounting with the existing state.

So, in short, When Strict Mode is on, React mounts components twice (in development only!) to check and let you know it has bugs. This is in development only and has no effect in code running in production.

If you just came here to "know" why your effects are being called twice that's it, that's the gist. You can spare reading this whole article, and go fix your effects
However, you can stay here and know some of the nuances.

But first, what is an effect?

According to beta react docs:

Some components need to synchronize with external systems. For example, you might want to control a non-React component based on the React state, set up a server connection, or send an analytics log when a component appears on the screen. Effects let you run some code after rendering so that you can synchronize your component with some system outside of React.

The after rendering part here is quite important. Therefore, you should keep this in mind before adding an effect to your component. For example, you might be setting some state in an effect based on a local state or a prop change.

function UserInfo({ firstName, lastName }) {
  const [fullName, setFullName] = useState('')

  // πŸ”΄ Avoid: redundant state and unnecessary Effect
  useEffect(() => {
    setFullName(`${firstName} ${lastName}`)
  }, [firstName, lastName])

  return <div>Full name of user: {fullName}</div>
}
Enter fullscreen mode Exit fullscreen mode

Just don't. Not only it is unnecessary, but it will cause an unnecessary second re-render when the value could've been calculated during render

function UserInfo({ firstName, lastName }) {
  // βœ… Good: calculated during initial render
  const fullName = `${firstName} ${lastName}`

  return <div>Full name of user: {fullName}</div>
}
Enter fullscreen mode Exit fullscreen mode

"But what if calculating some value during a render is not as cheap as our fullName variable here ?" Well, in that case, you can memoize an expensive calculation. You still don't have any need to use an Effect here

function SomeExpensiveComponent() {
  // ...

  const data = useMemo(() => {
    // Does no re-run unless deps changes
    return someExpensiveCalculaion(deps)
  }, [deps])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

This tells React to not re-calculate data unless deps changes. You only need to do this even when someExpensiveCalculaion is quite slow (say takes ~10 ms to run). But it's up to you. First see is it fast enough without a useMemo then build up from there. You can check the time it takes to run a piece of code using console.time or performance.now:

console.time('myBadFunc')
myBadFunc()
console.timeEnd('myBadFunc')
Enter fullscreen mode Exit fullscreen mode

You can see log like myBadFunc: 0.25ms or so. You can now decide whether to use useMemo or not. Also, even before using React.memo, you should first read this awesome article by Dan Abramov

What is useEffect

useEffect is a react hook that lets you to run side effects in your components. As discussed previously, effects run after a render and are caused by rendering itself, rather than by a particular event. (An event can be a user icon, for example, clicking a button). Hence useEffect should be only used for synchronization since it is not just fire-and-forget. The useEffect body is "reactive" in the sense whenever any dependencies in the dependency array change, the effect is re-fired. This is done so that the result of running that effect is always consistent and synchronized. But, as seen, this is not desirable.

It can be very tempting to use an effect here and there. For example, you want to filter a list of items based on a specific condition, like "cost less than β‚Ή500". You might think to write an effect for it, to update a variable whenever the list of items changes:

function MyNoobComponent({ items }) {
  const [filteredItems, setFilteredItems] = useState([])

  // πŸ”΄ Don't use effect for setting derived state
  useEffect(() => {
    setFilteredItems(items.filter(item => item.price < 500))
  }, [items])

  //...
}
Enter fullscreen mode Exit fullscreen mode

As discussed, it is inefficient. React will be needing to re-run your effects after updating state & calculating and updating UI. Since this time we are updating a state (filteredItems), React needs to restart all of this process from step 1! To avoid all this unnecessary calculation, just calculate the filtered list during render:

function MyNoobComponent({ items }) {
  // βœ… Good: calculating values during render
  const filteredItems = items.filter(item => item.price < 500)

  //...
}
Enter fullscreen mode Exit fullscreen mode

So, rule of thumb: 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). If this approach feels new to you, Thinking in React has some guidance on what should go into the state.

Also, you don't need an effect to handle events. (For example, a user clicking a button). Let's say you want to print a user's receipt:

function PrintScreen({ billDetails }) {
  // πŸ”΄ Don't use effect for event handlers
  useEffect(() => {
    if (billDetails) {
      myPrettyPrintFunc(billDetails)
    }
  }, [billDetails])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

I am guilty of writing this type of code in past. Just don't do it. Instead, in the parent component (where you might be setting billDetails as setBillDetails(), on a user's click of button, just do yourself a favor and print it there only):

function ParentComponent() {
  // ...

  return (
    // βœ… Good: useing inside event hanler
    <button onClick={() => myPrettyPrintFunc(componentState.billDetails)}>
      Print Receipt
    </button>
  )

  // ...
}
Enter fullscreen mode Exit fullscreen mode

The code above is now free of bugs caused by using useEffect in the wrong place. Suppose your application remembers the user state on page loads. Suppose user closes the tab because of some reason, and comes back, only to see a print pop-up on the screen. That's not a good user experience.

Whenever you are thinking about whether code should be in an event handler or in useEffect, think about why this code needs to be run. Was this because of something displayed to screen, or some action (event) performed by the user. If it is latter, just put it in an event handler. In our example above, the print was supposed to happen because the user clicked on a button, not because of a screen transition, or something shown to the user.

Fetching Data

One of the most used use cases of effects in fetching data. It is used all over the place as a replacement for componentDidMount. Just pass an empty array to array of dependencies and that's all:

useEffect(() => {
  // πŸ”΄ Don't - fetching data in useEffect _without_ a cleanup
  const f = async () => {
    setLoading(true)
    try {
      const res = await getPetsList()
      setPetList(res.data)
    } catch (e) {
      console.error(e)
    } finally {
      setLoading(false)
    }
  }

  f()
}, [])
Enter fullscreen mode Exit fullscreen mode

We have all seen and probably written this type of code before. Well, what's the issue?

  • First of all, useEffects are client-side only. That means they don't run on a server. So, the initial page rendered will only contain a shell of HTML with maybe a spinner
  • This code is prone to errors. For example, if the user comes back, clicks on the back button and then again re-opens the page. It is very much possible that the request that the first fired before the second one may get resolved after. So, the data in our state variable will be stale! Here, in the code above, it may not be a huge problem, but it is in the case of constantly changing data, or for eample querying data based on a search param while typing in input; it is. So, fetching data in effects leads to race conditions. You may not see it in development, or even in production, but rest assured that many of your users will surely experience this.
  • useEffect does not take care of caching, background updates, stale data, etc. which are necessary in apps that are not hobby.
  • This requires a lot of boilerplate to write by hand, and hence is not easy to manage and sustain.

Well, does that mean any fetching should not happen in an effect, no:

function ProductPage() {
  useEffect(() => {
    // βœ… This logic should be run in an effect, because it runs when page is displayed
    sendAnalytics({
      page: window.location.href,
      event: 'feedback_form',
    })
  }, [])

  useEffect(() => {
    // πŸ”΄ This logic is related to when an event is fired,
    // hence should be placed in an event handler, not in an effect
    if (productDataToBuy) {
      proceedCheckout(productDataToBuy)
    }
  }, [productDataToBuy])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

The analytics request made is okay to be kept in useEffect, since it will fire when the page is displayed. In Strict Mode, in development in React 18, useEffect will fire twice, but that is fine. (See here how to deal with that)

In many projects, you can see effects as a way of syncing queries to user inputs:

function Results({ query }) {
  const [res, setRes] = useState(null)

  // πŸ”΄ Fetching without cleaning up
  useEffect(() => {
    fetch(`results-endpoint?query=${query}}`).then(setRes)
  }, [query])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Maybe this seems opposite to what we discussed previously: to put fetching logic in an event handler. However, here the query may come from any source(user input, url, etc.) So, the results need to be synced with the query variable. However, consider the case we discussed before that, the user may press the back button and then forward button; then the data in res state variable may be stale or consider the query coming from user input and user typing fast. The query might change from p to po to pot to pota to potat to potato. This might initiate different fetches for each of those values, but it is not guaranteed that they will come back in that order. So, the results displayed may be wrong (of any of the previous queries). Hence, cleanup is required here, which ensures that results shown are not stale, and prevents race conditions:

function Results({ query }) {
  const [res, setRes] = useState(null)

  // βœ… Fetching with cleaning up
  useEffect(() => {
    let done = false

    fetch(`results-endpoint?query=${query}}`).then(data => {
      if (!done) {
        setRes(data)
      }
    })

    return () => {
      done = true
    }
  }, [query])

  // ...
}
Enter fullscreen mode Exit fullscreen mode

This makes sure that only the latest response from all responses is accepted.
Just handling race conditions with effects might seem like a lot of work. However, there is much more to data fetching such as caching, deduping, handling state data, background fetches, etc. Your framework might provide an efficient in-built data fetching mechanisms than using useEffect.

If you don't want to use a framework, you can extract all the above logic to a custom hook, or might use a library, like TanStack Query (previously known as useQuery) or swr.

So Far

  • useEffect fires twice in development in Strict Mode to point out that there will be bugs in production.
  • useEffect should be used when a component needs to synchronize with some external system since effects don't fire during rendering process and hence opt out of React's paradigm.
  • Don't use an effect for event handlers.
  • Don't use effect for derived state. (Heck, don't even use the derived state as long as possible, and calculate values during render).
  • Don't use effect for data fetching. If you are in a condition where you absolutely cannot avoid this, at least cleanup at the end of the effect.

Credits:

Much of the content above is shamelessly inspired from:

Liked it? Checkout my blog for more or Tweet this article

Top comments (12)

Collapse
brense profile image
Rense Bakker

For derived state, its highly recommended to use the useMemo hook.

const filteredItems = useMemo(() => items.filter(item => item.price < 500), [items])
Enter fullscreen mode Exit fullscreen mode

That way you avoid doing expensive array manipulation during each render cycle, even if the items did not change.

Collapse
shivamjjha profile image
Shivam Jha Author

Yeah, exactly. AlthoughfilteredItems above is technically not a state variable, but a derived value.

Collapse
fullstackchris profile image
Chris Frewin • Edited on

But the effect is identical if you use useEffect and then set this result to something like a filteredItems state variable. By definition, neither useMemo or useEffect will fire unless items changes (as long as the dependency array remains as just [items]. Granted, going the useEffect way of doing it would introduce a new state variable. But probably for something like this you would want a state variable for it anyway.

Collapse
brense profile image
Rense Bakker

What useMemo spits out is a state variable. If you change state in useEffect, you trigger another render cycle with new state. useMemo does not do this, it calculates the derived state in the current render cycle (during render), only if its dependencies have changed. useEffect is for side effects only.

Thread Thread
fullstackchris profile image
Chris Frewin

Well, I made an example to finally prove what I mean. You can clearly see by both methods (either useEffect or useMemo) that the component is render twice. So its two ways of doing the exact same thing, exact same 'efficiency', the benefit with useEffect is that you get your statevariable you can use later:

codesandbox.io/s/dreamy-wood-nrvfz...

Thread Thread
brense profile image
Rense Bakker

Yea... Because you put the console.log inside the useEffect hook for your useEffect example :B I updated your code example for you, so it's a fair comparison:
codesandbox.io/s/distracted-goldst...

Image description

In the image you can clearly see React 18 mounting the components and doing everything twice and then you can see the 2nd render caused by the useEffect hook updating state.

Really... this rule is not a joke: useEffect is only for side effects and should not be used for derived state, thats what useMemo is for, they didnt add this hook because they were bored. If you refuse to use the useMemo hook, you should STILL not use the useEffect hook for derived state. Just use a plain old javascript variable, it has better performance than triggering a 2nd rerender.

const [someStuff, setSomeStuff] = useState([])
const derivedStuff = someStuff.filter(stuff => stuff.isBetterForPerformance)
Enter fullscreen mode Exit fullscreen mode
Thread Thread
fullstackchris profile image
Chris Frewin

Ok, I admit that example was stupid since I would anyway probably do that as you mentioned just with JavaScript. Therefore, I still can't see a case you would want to do useMemo anywhere. See the newest example: the "JavaScript" only, as you said, is the most efficient, better than using either useMemo or useEffect:

codesandbox.io/s/festive-chatelet-...

I think the problem is from the beginning that the original useMemo example presented would probably only make sense if the 'derived state' of question actually has dependencies on other state variables, then maybe useMemo affords you some advantage, but the way I see it, when each dependency required for this derived state changes you're going to get a rerender, regardless of if you use useMemo or useEffect. This is why stupid arguments like this are such a waste of time and composition is one of the most important skills of writing good React components.

Thread Thread
brense profile image
Rense Bakker

No... there's a real functional difference. Updating state in a useEffect hook triggers another rerender on top of the rerender you were already getting from the state update that caused the dependencies of your useEffect hook to change. It's very important to understand this if you're going to be using React hooks, because otherwise you will run into performance issues and unexpected app behavior down the line. useMemo does not trigger a rerender, regardless of how many dependencies you pass into it.

You can opt for putting the logic to calculate your derived state directly into the render function of your component, however, that too can become a problem, for example if you try to render a lot of components at the same time and all of them have a bunch of junk happening inside their render functions, your app will become slow, to a point where it becomes unusable.

It's not an argument, it's a fact that useMemo works this way. It memoizes expensive calculations, so they dont have to happen on each render if the dependencies did not change.

Collapse
shivamjjha profile image
Shivam Jha Author

The issue is when you call useEffect for this, this will not happen during initial render phase.

State set -> react realizes it needs to fire an effect -> usEffect runs -> state set again (filteredItems) -> re render

So you see this is not very performant.
While if you use useMemo (or just simply calculate result without memo), react can calculate result during render which is more desirable

Collapse
apperside profile image
Apperside

You say "don't use useEfffect for event handlers" and you are totally right, but unless you are using a primitive html element, the best solution for callbacks and event handlers is not in-lining it, rather than you should use useCallback in that case


`function ParentComponent() {

  const myCallback = useCallback(()=>{
    console.log("callback fired");
  },[]);

  return (
    // βœ… Good: using useCallback for memoizing the callback function
    <button onClick={myCallback}>
      Print Receipt
    </button>
  )

  // ...
}
`
Enter fullscreen mode Exit fullscreen mode
Collapse
shivamjjha profile image
Shivam Jha Author

useCallback does make sense; but only when the function creation is expensive. If you are wrapping a simple function (as in example); it is actually slower that without useCallback. A new hook useEvent is coming into React soon that would solve this problem (no need to pass dependencies): github.com/reactjs/rfcs/blob/useev...

Until then, we should sure not to pre-maturely opt for "optimizations" if you will.
Kent C Dodds has written a great article on this: kentcdodds.com/blog/usememo-and-us...

Collapse
apperside profile image
Apperside

Hi,
I have read that article, and is really really cool.
However, I think that useCallback should be used not only when the function creation is expensive: the main purpose of useCallback is to memorize the function, hence it's reference, in order to be able to pass it as a prop to a component without causing it (and all of its childs) to rerender because of a function prop change.

Now, if the callback is used to be passed as a prop to a "native" component (like html button for example), it does not have any child and so it shouldn't cause so much troubles. But, for example, if the callback is to be passed to a component's props, and that component then will pass it to a button component, that function reference change will cause the component that renders the button to re-render all its childs as well

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.