207

With React's new Effect Hooks, I can tell React to skip applying an effect if certain values haven't changed between re-renders - Example from React's docs:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

But the example above applies the effect upon initial render, and upon subsequent re-renders where count has changed. How can I tell React to skip the effect on the initial render?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
uturnr
  • 2,516
  • 2
  • 12
  • 11

11 Answers11

220

As the guide states,

The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.

In this example from the guide it's expected that count is 0 only on initial render:

const [count, setCount] = useState(0);

So it will work as componentDidUpdate with additional check:

useEffect(() => {
  if (count)
    document.title = `You clicked ${count} times`;
}, [count]);

This is basically how custom hook that can be used instead of useEffect may work:

function useDidUpdateEffect(fn, inputs) {
  const didMountRef = useRef(false);

  useEffect(() => {
    if (didMountRef.current) { 
      return fn();
    }
    didMountRef.current = true;
  }, inputs);
}

Credits go to @Tholle for suggesting useRef instead of setState.

Kostas Minaidis
  • 4,681
  • 3
  • 17
  • 25
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 8
    Where is the suggestion for `useRef` over `setState`? I don't see it on this page and I would like to understand why. – rob-gordon Jun 24 '19 at 15:09
  • 10
    @rob-gordon This was a comment that was deleted after the answer was updated. The reasoning is that useState woukld result in unnecessary component update. – Estus Flask Jun 24 '19 at 15:18
  • 2
    This approach works, but it violates the react-hooks/exhaustive-deps linter rule. I.e. fn is not given in the array of deps. Anyone have an approach that doesn't violate React best practices? – Justin Lang Oct 01 '19 at 22:22
  • 13
    @JustinLang React linter rules != best practices, they only try to address common hook problems. ESLint rules aren't intelligent and may result in false positives or negatives. As long as the logic behind a hook is correct, a rule can be safely ignored with eslint-disable or eslint-disable-next comment. In this case `fn` shouldn't be provided as input. See the explanation, https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies . If something inside `fn` introduces deps, it's more like they should be provided as `inputs` directly. – Estus Flask Oct 02 '19 at 11:33
  • 3
    **Note**: If you are using multiple useEffects that check for didMountRef, make sure only the last one (on bottom) is setting didMountRef to false. React goes through useEffects in order! – Dror Bar Apr 18 '21 at 11:07
  • This worked for me but I had to disable React.StrictMode because it renders components twice, which circumvented the intention of the ref guard. – unremarkable Sep 10 '22 at 02:37
  • I made a hook for that called `useDidMount()` and then I am using it as `const didMount = useDidMount()`, where `didMount` is a boolean – Normal Dec 09 '22 at 14:17
  • This works but if you need a cleanup function it doesn't work for me. Is there a way?, event though we are returning func(); it will not call the cleanup function when using this hook. – Madtin Feb 08 '23 at 10:46
  • @Madtin Can you provide a demo? There's supposed to be no inconsistencies due to the simplicity of code, `return fn()` just returns cleanup function, considering that `fn` returns it – Estus Flask Feb 08 '23 at 11:16
  • Just in case someone needs it in Typescript, the type for `fn` is `EffectCallback` and the type for `inputs` is `DependencyList` (and is optional in `useEffect`). Both types can be imported from `react`. – ftoyoshima Apr 26 '23 at 20:45
  • This won't work in strict mode, right? – Boris Boskovic May 15 '23 at 09:04
  • @BorisBoskovic I didn't notice any problems with strict mode myself, despite the comment above. I wouldn't actually expect them because the hook follows normal lifecycle of a component. If there's a problem associated with it that can be reproduced, please, notify me, I'll try to check. – Estus Flask May 15 '23 at 09:32
  • @EstusFlask Sorry for late response, It actually worked as expected. I was doing it wrong. – Madtin May 23 '23 at 08:59
  • 1
    @EstusFlask the effect will actually run during first render under Strict mode, because react does extra cleanup - setup cycle. Reproduced here: https://codesandbox.io/s/quirky-tharp-3sdz4x?file=/src/App.js – ondrej.par Jul 27 '23 at 20:21
112

Here's a custom hook that just provides a boolean flag to indicate whether the current render is the first render (when the component was mounted). It's about the same as some of the other answers but you can use the flag in a useEffect or the render function or anywhere else in the component you want. Maybe someone can propose a better name.

import { useRef, useEffect } from 'react';

export const useIsMount = () => {
  const isMountRef = useRef(true);
  useEffect(() => {
    isMountRef.current = false;
  }, []);
  return isMountRef.current;
};

You can use it like:

import React, { useEffect } from 'react';

import { useIsMount } from './useIsMount';

const MyComponent = () => {
  const isMount = useIsMount();

  useEffect(() => {
    if (isMount) {
      console.log('First Render');
    } else {
      console.log('Subsequent Render');
    }
  });

  return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};

And here's a test for it if you're interested:

import { renderHook } from '@testing-library/react-hooks';

import { useIsMount } from '../useIsMount';

describe('useIsMount', () => {
  it('should be true on first render and false after', () => {
    const { result, rerender } = renderHook(() => useIsMount());
    expect(result.current).toEqual(true);
    rerender();
    expect(result.current).toEqual(false);
    rerender();
    expect(result.current).toEqual(false);
  });
});

Our use case was to hide animated elements if the initial props indicate they should be hidden. On later renders if the props changed, we did want the elements to animate out.

Scotty Waggoner
  • 3,083
  • 2
  • 26
  • 40
  • Import changed from `react-hooks-testing-library` to `@testing-library/react-hooks` https://github.com/testing-library/react-hooks-testing-library/releases/tag/v1.0.0 – Scotty Waggoner Jul 15 '19 at 23:28
  • 2
    Why did you choose "isMount" and not "didMount" or "isMounted" ? – vsync Nov 07 '19 at 10:33
  • 5
    Guess I was thinking about whether the current render is the render where the mount was happening. Ya I agree, it sounds a little weird coming back to it now. But it's true on the first render and false after so your suggestions sound misleading. `isFirstRender` could work. – Scotty Waggoner Nov 07 '19 at 17:29
  • 4
    Thanks for the hook! I agree with @ScottyWaggoner , `isFirstRender` is a better name – abumalick Apr 11 '20 at 12:12
  • Thanks for your answer. I am using your hook the way like first value false, after mounting true and renamed didMount :) – Tugrul Emre Atalay May 30 '20 at 15:49
  • 1
    Adding the test is FIRE! Great response. – wlh Jan 29 '21 at 20:14
  • Thank you so much for this ! I totally agree about the name. `isFirstRender` makes sense. I have used it in my project with this name already =) – Enes Oct 06 '21 at 23:24
  • 1
    Awesome stuff!! – Alex Dunlop Nov 25 '21 at 02:53
  • 1
    Have been looking for something like this for too long, great stuff! Adding the test is a great touch. Should be top answer! – mattmakesnoise Mar 03 '22 at 13:54
  • Recently I used this with Next.js to wait until after hydration to update dates to a local timezone to prevent hydration errors where the client UI didn't match what was server rendered. I named it `useIsHydrated`, flipped the initial state, and used `useState` instead of `useRef` to force a rerender when the value changed. – Scotty Waggoner Nov 16 '22 at 22:40
36

I use a regular state variable instead of a ref.

// Initializing didMount as false
const [didMount, setDidMount] = useState(false)

// Setting didMount to true upon mounting
useEffect(() => { setDidMount(true) }, [])

// Now that we have a variable that tells us wether or not the component has
// mounted we can change the behavior of the other effect based on that
const [count, setCount] = useState(0)
useEffect(() => {
  if (didMount) document.title = `You clicked ${count} times`
}, [count])

We can refactor the didMount logic as a custom hook like this.

function useDidMount() {
  const [didMount, setDidMount] = useState(false)
  useEffect(() => { setDidMount(true) }, [])

  return didMount
}

Finally, we can use it in our component like this.

const didMount = useDidMount()

const [count, setCount] = useState(0)
useEffect(() => {
  if (didMount) document.title = `You clicked ${count} times`
}, [count])

UPDATE Using useRef hook to avoid the extra rerender (Thanks to @TomEsterez for the suggestion)

This time our custom hook returns a function returning our ref's current value. U can use the ref directly too, but I like this better.

function useDidMount() {
  const mountRef = useRef(false);

  useEffect(() => { mountRef.current = true }, []);

  return () => mountRef.current;
}

Usage

const MyComponent = () => {
  const didMount = useDidMount();

  useEffect(() => {
    if (didMount()) // do something
    else // do something else
  })

  return (
    <div>something</div>
  );
}

On a side note, I've never had to use this hook and there are probably better ways to handle this which would be more aligned with the React programming model.

Amiratak88
  • 1,204
  • 12
  • 18
  • 14
    `useRef` is better suited for that because `useState` will cause an additional and useless render of the component: https://codesandbox.io/embed/youthful-goldberg-pz3cx – Tom Esterez Aug 19 '19 at 17:45
  • you have an error in your `useDidMount` Function. You have to encapsulate the `mountRef.current = true` in `useEffect()` with curly brackets `{ ...}`. Leaving them is like writing `return mountRef.current = true`, and that cause an error like `An effect function must not return anything besides a function, which is used for clean-up. You returned: true` – suther Nov 13 '20 at 08:00
36

I found a solution that is more simple and has no need to use another hook, but it has drawbacks.

useEffect(() => {
  // skip initial render
  return () => {
    // do something with dependency
  }
}, [dependency])

This is just an example that there are others ways of doing it if your case is very simple.

The drawback of doing this is that you can't have a cleanup effect and will only execute when the dependency array changes the second time.

This isn't recommended to use and you should use what the other answers are saying, but I only added this here so people know that there is more than one way of doing this.

Edit:

Just to make it more clear, you shouldn't use this approach to solving the problem in the question (skipping the initial render), this is only for teaching purpose that shows you can do the same thing in different ways. If you need to skip the initial render, please use the approach on other answers.

Sanket Shah
  • 2,888
  • 1
  • 11
  • 22
Vencovsky
  • 28,550
  • 17
  • 109
  • 176
  • 1
    I just learned something. I didn't think this would work, then I tried and it actually does. Thank you! – zimmerbimmer Apr 11 '20 at 21:30
  • 2
    React should provide a better way for this, but this issue is open on Github and the proposal is to write a custom solution yourself (which is utter non-sense). – html_programmer Jul 12 '20 at 11:18
  • 1
    The problem with that one is that it will be executed on unMount as well, which is not perfect. – Nick Alves Jul 21 '20 at 21:16
  • 1
    @ncesar if this was the only answer that worked, probably you are doing something wrong, because the other answer are the correct ones and mine is just for teaching purpos – Vencovsky Dec 04 '20 at 11:43
27

Let me introduce to you react-use.

npm install react-use

Wanna run:

only after first render? -------> useUpdateEffect

only once? -------> useEffectOnce

check is it first mount? -------> useFirstMountState

Want to run effect with deep compare, shallow compare or throttle? and much more here.

Don't want to install a library? Check the code & copy. (maybe a star for the good folks there too)

Best thing is one less thing for you to maintain.

cYee
  • 1,915
  • 1
  • 16
  • 24
  • 4
    Looks like a really good package – yalcinozer Jan 12 '22 at 08:46
  • 2
    Fantastic resource, so glad I found this! – drichar Mar 31 '22 at 06:22
  • Unclear what's the difference between `useUpdateEffect` & `useUpdateEffect`? Both run only once – vsync May 15 '23 at 12:46
  • It's a very bloated library and it's unclear if it was made with tree-shaking in mind. Also it bloats the `node_modules` if all you need is to run `useEffect` not on mount... – vsync May 15 '23 at 12:47
  • `Unclear what's the difference between useUpdateEffect & useUpdateEffect? Both run only once` Do you mean `useUpdateEffect` and `useEffectOnce`? `useUpdateEffect` is exactly like `useEffect` but skip the first run. Use can see it here: https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md – cYee May 16 '23 at 01:44
  • For tree-shaking, you can check here: https://github.com/streamich/react-use/blob/master/docs/Usage.md Example: You can import like this `import useUpdateEffect from "react-use/lib/useUpdateEffect"` – cYee May 16 '23 at 01:47
9

A TypeScript and CRA friendly hook, replace it with useEffect, this hook works like useEffect but won't be triggered while the first render happens.

import * as React from 'react'

export const useLazyEffect:typeof React.useEffect = (cb, dep) => {
  const initializeRef = React.useRef<boolean>(false)

  React.useEffect((...args) => {
    if (initializeRef.current) {
      cb(...args)
    } else {
      initializeRef.current = true
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, dep)
}
Losses Don
  • 923
  • 10
  • 14
5

Here is my implementation based on Estus Flask's answer written in Typescript. It also supports cleanup callback.

import { DependencyList, EffectCallback, useEffect, useRef } from 'react';

export function useDidUpdateEffect(
  effect: EffectCallback,
  deps?: DependencyList
) {
  // a flag to check if the component did mount (first render's passed)
  // it's unrelated to the rendering process so we don't useState here
  const didMountRef = useRef(false);

  // effect callback runs when the dependency array changes, it also runs
  // after the component mounted for the first time.
  useEffect(() => {
    // if so, mark the component as mounted and skip the first effect call
    if (!didMountRef.current) {
      didMountRef.current = true;
    } else {
      // subsequent useEffect callback invocations will execute the effect as normal
      return effect();
    }
  }, deps);
}

Live Demo

The live demo below demonstrates the different between useEffect and useDidUpdateEffect hooks

Edit 53179075/with-useeffect-how-can-i-skip-applying-an-effect-upon-the-initial-render

NearHuscarl
  • 66,950
  • 18
  • 261
  • 230
3

I was going to comment on the currently accepted answer, but ran out of space!

Firstly, it's important to move away from thinking in terms of lifecycle events when using functional components. Think in terms of prop/state changes. I had a similar situation where I only wanted a particular useEffect function to fire when a particular prop (parentValue in my case) changes from its initial state. So, I created a ref that was based on its initial value:

const parentValueRef = useRef(parentValue);

and then included the following at the start of the useEffect fn:

if (parentValue === parentValueRef.current) return;
parentValueRef.current = parentValue;

(Basically, don't run the effect if parentValue hasn't changed. Update the ref if it has changed, ready for the next check, and continue to run the effect)

So, although other solutions suggested will solve the particular use-case you've provided, it will help in the long run to change how you think in relation to functional components.

Think of them as primarily rendering a component based on some props.

If you genuinely need some local state, then useState will provide that, but don't assume your problem will be solved by storing local state.

If you have some code that will alter your props during a render, this 'side-effect' needs to be wrapped in a useEffect, but the purpose of this is to have a clean render that isn't affected by something changing as it's rendering. The useEffect hook will be run after the render has completed and, as you've pointed out, it's run with every render - unless the second parameter is used to supply a list of props/states to identify what changed items will cause it to be run subsequent times.

Good luck on your journey to Functional Components / Hooks! Sometimes it's necessary to unlearn something to get to grips with a new way of doing things :) This is an excellent primer: https://overreacted.io/a-complete-guide-to-useeffect/

Sanket Shah
  • 2,888
  • 1
  • 11
  • 22
Shiraz
  • 2,310
  • 24
  • 26
2

You can use custom hook to run use effect after mount.

const useEffectAfterMount = (cb, dependencies) => {
  const mounted = useRef(false);

  useEffect(() => {
    if (mounted.current) {
      return cb();
    }
    mounted.current = true;
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};

Here is the typescript version:

const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
  const mounted = useRef(false);

  useEffect(() => {
    if (mounted.current) {
      return cb();
    }
    mounted.current = true;
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};

Example:

useEffectAfterMount(() => {
  document.title = `You clicked ${count} times`;
}, [count])
Foyez
  • 334
  • 3
  • 9
  • You have inversed true and false, it's ```const mounted = useRef(false)``` and then ```mounted.current = true``` – Mik378 Feb 16 '23 at 15:15
0

Below solution is similar to above, just a little cleaner way i prefer.

const [isMount, setIsMount] = useState(true);
useEffect(()=>{
        if(isMount){
            setIsMount(false);
            return;
        }
        
        //Do anything here for 2nd render onwards
}, [args])
rahulxyz
  • 437
  • 4
  • 12
0

1、I choose the npm package ahooks to solve my issue, you should run npm i ahooks first.
2、Then in your code

  import { useUpdateEffect } from "ahooks";
  useUpdateEffect(() => {
    // do something if deps change but skipping the initial render.
  }, deps);
West
  • 33
  • 4