How to call loading function with React useEffect only once
Asked Answered
F

18

547

The useEffect React hook will run the passed-in function on every change. This can be optimized to let it call only when the desired properties change.

What if I want to call an initialization function from componentDidMount and not call it again on changes? Let's say I want to load an entity, but the loading function doesn't need any data from the component. How can we make this using the useEffect hook?

class MyComponent extends React.PureComponent {
    componentDidMount() {
        loadDataOnlyOnce();
    }
    render() { ... }
}

With hooks this could look like this:

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire on every change :(
    }, [...???]);
    return (...);
}
Filtrate answered 2/11, 2018 at 14:58 Comment(0)
P
864

If you only want to run the function given to useEffect after the initial render, you can give it an empty array as second argument.

function MyComponent() {
  useEffect(() => {
    loadDataOnlyOnce();
  }, []);

  return <div> {/* ... */} </div>;
}
Pancho answered 2/11, 2018 at 15:1 Comment(14)
Alternatively if there are params you use to fetch the data (e.g. a user id) you could pass the user id in that array and if it changes the component will refetch the data. Many of the use cases will work like that.Leatherworker
yep... more about skipping is documented here: reactjs.org/docs/…Chanukah
This seems like the simplest answer, but ESLint complains... see other answer on this thread https://mcmap.net/q/73488/-how-to-call-loading-function-with-react-useeffect-only-onceRetainer
Just pass loadDataOnlyOnce into the dependencies array. Does that work?Wrung
No, because when loadDataOnlyOnce changes (not in this example, but lint wouldn't complain about non-local variables anyway), it will re-run the effect. The solution would be either to make a separate function for the hook like in another answer here (effectively fooling ESLint), or have a useRef with boolean value that you set after the first run and don't run again if it's set.Mateya
What's wrong with useEffect(loadDataOnlyOnce, []);?Prosy
how about with variable inside []? it always runs 2 times at least.Fiscus
Please check it out :stackoverflow.com/q/71110751/17570782Playtime
Aug 2022, this is still a good answer.Dispatcher
loadDataOnlyOnce will called two times. May it's no noticeable. But it's two times expensive on paid services like API call or cloud database reads. loadDataOnlyOnce function should prevents two times call in itself.Calif
Heads up: this won't work anymore starting with React 18. This piece of code will run twice.Palliate
Heads heads up: useEffect runs twice on mount in React 18 but only in dev mode. They say this is to help with debugging (but it's actually just a bit confusing). See here: beta.reactjs.org/reference/react/useEffect#usageCajolery
This seems like the most recent docs on useEffect firing twice in React 18: react.dev/learn/…Outrelief
The double firing makes sense according to the docs. It ensures that cleanup correctly undoes what the useEffect function does.Deadhead
T
196

TL;DR

useEffect(yourCallback, []) - will trigger the callback only after the first render.

Detailed explanation

useEffect runs by default after every render of the component (thus causing an effect).

When placing useEffect in your component you tell React you want to run the callback as an effect. React will run the effect after rendering and after performing the DOM updates.

If you pass only a callback - the callback will run after each render.

If passing a second argument (array), React will run the callback after the first render and every time one of the elements in the array is changed. for example when placing useEffect(() => console.log('hello'), [someVar, someOtherVar]) - the callback will run after the first render and after any render that one of someVar or someOtherVar are changed.

By passing the second argument an empty array, React will compare after each render the array and will see nothing was changed, thus calling the callback only after the first render.

Twocycle answered 2/4, 2019 at 18:38 Comment(0)
F
185

useMountEffect hook

Running a function only once after component mounts is such a common pattern that it justifies a hook of its own that hides implementation details.

const useMountEffect = (fun) => useEffect(fun, [])

Use it in any functional component.

function MyComponent() {
    useMountEffect(function) // function will run only once after it has mounted. 
    return <div>...</div>;
}

About the useMountEffect hook

When using useEffect with a second array argument, React will run the callback after mounting (initial render) and after values in the array have changed. Since we pass an empty array, it will run only after mounting.

Fahey answered 26/6, 2019 at 7:54 Comment(21)
I highly prefer your answer, as ESLint rule "react-hooks/exhaustive-deps" will always fail on empty dependency lists. And for example the famous create-react-app template will enforce that rule.Quadrennial
Totally agree with @Dynalon. This should be the accepted solution as it does not interfer with the ESLint ruleAndrow
Thanks @Quadrennial and Mikado68 :-). The decision is a privilege of the OP. I was notified about your comments, but the OP wasn't. You can suggest it to him by commenting directly on the question.Fahey
@Androw see my comment aboveFahey
Now you can use useMount when your effect function needs something from props but never needs to run again even if that value changes without linter warnig: useEffect(()=>console.log(props.val),[]) will have missing dependency warning but useMount(()=>console.log(props.val)) won't cause a warning but "does work". I'm not sure if there will be a problem with concurrent mode though.Crockett
Unless I'm missing something, ESLint still complains inside the custom useMountEffect hook. I've turned it off with eslint-disable-line, and better to that just once inside the new hook file, but still a little annoying. Seems like the rule should allow this.Eyesight
@DávidMolnár, please see Dynalon and Mikado68 comments above.Fahey
I like this one :) Same reason as previously state; the ESLint rule won't complain about this one, plus the name of it is easier to understand than an empty arrayLanalanae
This is the solution I was looking for, thank you @BenCarpHindgut
I don't quite understand... "react-hooks/exhaustive-deps" still whines about the empty array in const useMountEffect = (fun) => useEffect(fun, [])Aleksandropol
@VincentChan, it whines about the empty array, but it only whines ones. It doesn't whine when you call useMountEffect. Obviously you can ignore it, and you only need to ignore it once. You are hiding this noise/clutter from your componentsFahey
Thanks! Though I think this points to a flaw in "react-hooks/exhaustive-deps", particularly since this is the canonical way to run things on mount. This "solution" functionally moves the problem from the component elsewhere instead of fundamentally solving the issue with empty deps.Aleksandropol
What about if we want to call the function with arguments ?Ash
There are numerous comments here about flaws in ESLint rules for react-hooks/exhaustive-deps and I am here trying to figure out a solution to a problem similar to that of the OP. But I'm not sure that the ESLint rules really are mistaken here. That linting rule isn't just checking to see if your hook's immediate dependencies are using values or functions that will change, but also whether any dependent functions might be using values or functions that will change.Dierdre
This will not "get around" the ESLint rule b/c it will still call out that useEffect has a dependency: fun.Dierdre
@flyingace, the hook useMountEffect is intended by definition to run only on mount. If mount effect is what you actually need dependency changes are irrelevant. react-hooks/exhaustive-deps lint rule for providing dependencies to useEffect are valuable (can prevent bug), but not in the context of mount effect. Therefore, if mount effect is what's needed, ignoring that rule is necessary. And if we need to ignore that rule it is better to do it just once.Fahey
@BenCarp I'm not discussing what the intention of the function is, I'm saying that actually using without listing fun as a dependency is still going to result in the ESLinting error being thrown. Certainly it can be ignored or turned off, but I'm not sure what the value of doing so in this custom hook vs. doing so in the function that you're passing in.Dierdre
@flyingace, can you share a minimal reproducible example (perhaps stackblitz or codesandbox) recreating the error?Fahey
seems you get the eslint exhaustive deps error for this syntax. I tried this and found that, duh, because a function is always non referential ( or however you say it ) that it defeated the whole purpose. don't do this --->>> export const useMountEffect = (fun) => useEffect(fun, [fun]); so I guess if you create a separate module and escape the rule in that one place, that isolates and abstracts the implementation and the work aroundGlans
Any Idea how to add typing for this in typescript?Henryson
@kblst, type the cb parameter as React.EffectCallbackFahey
Q
95

We have to stop thinking in component-life-cycle-methods (i.e. componentDidMount). We have to start thinking in effects. React effects are different from old-style class-life-cycle-methods.

By default effects run after every render cycle, but there are options to opt out from this behaviour. To opt out, you can define dependencies that mean that an effect is only carried out when a change to one of the dependencies is made.

If you explicitly define, that an effect has no dependecy, the effect runs only once, after the first render-cycle.

1st solution (with ESLint-complaint)

So, the first solution for your example would be the following:

function MyComponent() {

    const loadDataOnlyOnce = () => {
      console.log("loadDataOnlyOnce");
    };

    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, []);
    return (...);
}

But then the React Hooks ESLint plugin will complain with something like that:

React Hook useEffect has missing dependency: loadDataOnlyOnce. Either include it or remove the dependency array.

At first this warning seems annoying, but please don't ignore it. It helps you code better and saves you from "stale closures". If you don't know what "stale closures" are, please read this great post.

2nd solution (the right way, if dependency is not dependent on component)

If we add loadDataOnlyOnce to the dependency array, our effect will run after every render-cycle, because the reference of loadDataOnlyOnce changes on every render, because the function is destroyed(garbarge-collected) and a new function is created, but that's exactly what we don't want.

We have to keep the same reference of loadDataOnlyOnce during render-cycles.

So just move the function-definition above:

const loadDataOnlyOnce = () => {
  console.log("loadDataOnlyOnce");
};

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, [loadDataOnlyOnce]);
    return (...);
}

With this change you ensure that the reference of loadDataOnlyOnce will never change. Therefore you can also safely add the reference to the dependency array.

3rd solution (the right way, if dependency is dependent on component)

If the dependency of the effect (loadDataOnlyOnce), is dependent on the component (need props or state), there's React's builtin useCallback-Hook.

An elementary sense of the useCallback-Hook is to keep the reference of a function identical during render-cycles.

function MyComponent() {
    const [state, setState] = useState("state");

    const loadDataOnlyOnce = useCallback(() => {
      console.log(`I need ${state}!!`);
    }, [state]);

    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only when loadDataOnlyOnce-reference changes
    }, [loadDataOnlyOnce]);
    return (...);
}
Quick answered 21/9, 2021 at 7:4 Comment(6)
This answer deserves more attention by beginner React developers.Beghtol
@JusticeBringer I would say not only for beginners. It is not a straightforward concept.Gabey
Forth option. Place the function content directly into useEffect. This also removes "eslint" error. Thanks for explaining the concept by the way.Intramuscular
When to use useEventEffect() vs useCallback()?Rippy
@Rippy reactjs.org/docs/hooks-reference.html#usecallback useEventCallback is not an official react hook.Quick
It may be worth flagging that for anyone using create-react-app in development mode, even the cases above which should run once will actually run twice as create-react-app and strict model intentionally double-invoke some functions: https://mcmap.net/q/74653/-why-is-my-function-being-called-twice-in-reactConventioner
M
40
function useOnceCall(cb, condition = true) {
  const isCalledRef = React.useRef(false);

  React.useEffect(() => {
    if (condition && !isCalledRef.current) {
      isCalledRef.current = true;
      cb();
    }
  }, [cb, condition]);
}

and use it.

useOnceCall(() => {
  console.log('called');
})

or

useOnceCall(()=>{
  console.log('Fetched Data');
}, isFetched);
Mazel answered 4/12, 2020 at 21:28 Comment(3)
Thanks! Saved my day. Ideal for calling functions once, but only after some state needs to be loaded.Wun
I actually got here because i got curious if there is such a thing which has this ref logic built in so you don't have to create a ref inside of a component but hook is taking care of it for you?Nunez
ref is required for conditional logic. if you want to run a code after something, this hook will handle it for you. Please check the second example.Mazel
D
32

Pass an empty array as the second argument to useEffect. This effectively tells React, quoting the docs:

This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.

Here's a snippet which you can run to show that it works:

function App() {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUser(data.results[0]);
      });
  }, []); // Pass empty array to only run once on mount.
  
  return <div>
    {user ? user.name.first : 'Loading...'}
  </div>;
}

ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>
Decimeter answered 11/11, 2018 at 21:28 Comment(0)
S
17

I had this issue with React 18. This is how I handled it:

import { useEffect, useRef } from "react";

export default function Component() {
    const wasCalled = useRef(false);

    useEffect(() => {
        if(wasCalled.current) return;
        wasCalled.current = true;
            
         /* CODE THAT SHOULD RUN ONCE */

    }, []);

    return <div> content </div>;
}

Check here how they explain why useEffect is called more than once.

Spearing answered 12/7, 2022 at 13:41 Comment(4)
Can you add more specifics why this is the solution for React 18?Scoria
Sorry I meant that I had this issue with React 18.Spearing
Weird that useEffectOnce from react-use doesn't work but this does.Festal
this worked for me too even with a dependency in [] which didn't work with normal codeSouthwards
I
12

I like to define a mount function, it tricks EsLint in the same way useMount does and I find it more self-explanatory.

const mount = () => {
  console.log('mounted')
  // ...

  const unmount = () => {
    console.log('unmounted')
    // ...
  }
  return unmount
}
useEffect(mount, [])

Indigestion answered 12/10, 2019 at 2:20 Comment(0)
H
6

leave the dependency array blank . hope this will help you understand better.

   useEffect(() => {
      doSomething()
    }, []) 

empty dependency array runs Only Once, on Mount

useEffect(() => {
  doSomething(value)
}, [value])  

pass value as a dependency. if dependencies has changed since the last time, the effect will run again.

useEffect(() => {
  doSomething(value)
})  

no dependency. This gets called after every render.

Hanleigh answered 16/11, 2020 at 15:57 Comment(0)
G
4

Here is my version of Yasin's answer.

import {useEffect, useRef} from 'react';

const useOnceEffect = (effect: () => void) => {
  const initialRef = useRef(true);

  useEffect(() => {
    if (!initialRef.current) {
      return;
    }
    initialRef.current = false;
    effect();
  }, [effect]);
};

export default useOnceEffect;

Usage:

useOnceEffect(
  useCallback(() => {
    nonHookFunc(deps1, deps2);
  }, [deps1, deps2])
);
Guinn answered 10/8, 2021 at 14:0 Comment(0)
J
2

This does not answer your question exactly but may have the same intended affect of only running a function once and after the first render. Very similar to the componentDidMount function. This uses useState instead of useEffect to avoid dependency lint errors. You simply pass a self-executing anonymous function as the first argument to useState. As an aside, I'm not sure why React doesn't simply provide a hook that does this.

import React, { useState } from "react"

const Component = () => {

  useState((() => {
    console.log('componentDidMountHook...')
  }))

  return (
    <div className='component'>Component</div>
  )
}

export default Component
Jude answered 19/1, 2022 at 4:51 Comment(0)
C
2

You should do the first way below

useEffect hook has 3ways to use.

  1. runs once when the component will be rendered:

     useEffect(()=>{
         // your code
     },[])
    
  2. runs everytime when these x1,x2,... dependencies have been chaged:

     useEffect(()=>{
     // your code
     },[x1,x2,...])
    
  3. runs after updating anything and also on rendering the component:

      useEffect(()=>{
     // your code
      })
    
Casmey answered 17/11, 2023 at 16:58 Comment(0)
T
1

I found out after some time spend on the internet. useEffect fires once on component mount, then componennt unmounts and mounts again, useEffect fires again. You have to check more on React docs, why they do that.

So, I used custom hook for that. On unmount you have to change your useRef state. In this case do not forget a return statement: when component unmounts, useEffect runs cleanup function after return.

import React, { useEffect, useRef } from "react"

const useDidMountEffect = (
  func: () => void,
  deps: React.DependencyList | undefined
) => {
  const didMount = useRef(false)

  useEffect(() => {
    if (didMount.current) {
      func()
    }
    return () => {
      didMount.current = true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps)
}

export default useDidMountEffect

Use it like normal useEffect:

  useDidMountEffect(() => {
    // your action    
  }, [])
Tommyetommyrot answered 8/8, 2022 at 8:46 Comment(0)
G
0

window.onpageshow works even if the user presses the back button to navigate to the page, unlike passing an empty array as second argument of the use-effect hook which does not fire when returning to the page via the back button (thus not on every form of initial page load).

 useEffect(() => {    
     window.onpageshow = async function() {
      setSomeState(false)
      let results = await AsyncFunction() 
       console.log(results, 'Fires on on first load, 
        refresh, or coming to the page via the back button')
    };
 };
Gill answered 26/5, 2022 at 15:15 Comment(0)
P
0

I found that with the once function from lodash the problem may be solved concisely and elegantly.

import { once } from "lodash";
import { useEffect, useRef } from "react";

export const useEffectOnce = (cb: () => void) => {
  const callBackOnce = useRef(once(cb)).current;
  useEffect(() => callBackOnce(), [callBackOnce]);
};
Puttier answered 11/10, 2022 at 14:28 Comment(0)
E
0

In the future we will have useEffectEvent, see this official react article:

Use a special Hook called useEffectEvent to extract this non-reactive logic out of your Effect:

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]); // ✅ All dependencies declared
  // ...

Here, onConnected is called an Effect Event. It’s a part of your Effect logic, but it behaves a lot more like an event handler. The logic inside it is not reactive, and it always “sees” the latest values of your props and state.

In their example even if theme changed (or the callback in the case of the question) the effect event is not run again (i.e. useEffectEvent doesn't have any dependencies).

So to answer the question (assuming loadDataOnlyOnce depends on some props/state or other variable in scope which would execute it again and again; otherwise just put it outside of the component body; react advices against useCallback for flow control):

function MyComponent() {
    const loadDataOnlyOnceEvent = useEffectEvent(loadDataOnlyOnce)

    useEffect(() => {
        loadDataOnlyOnceEvent();
    }, []);
    return (...);
}
Epicycle answered 30/5, 2023 at 11:46 Comment(0)
F
-1

Incase you just call the function in useeffect after render you add an empty array as the second argument for the useeffect

useEffect=(()=>{
   functionName(firstName,lastName);
},[firstName,lastName])

calling a custom hooks

adding a empty array for rendering

Freely answered 5/12, 2021 at 14:3 Comment(0)
P
-1

You pass blank array in useEffect so its run once

import React, { useEffect } from "react";

function MyComponent() {
  useEffect(() => {
    // This code will run only once when the component mounts
  }, []); // The empty dependency array means the effect runs only once

  return <div>{/* Your component JSX */}</div>;
}
Plattdeutsch answered 7/11, 2023 at 16:14 Comment(1)
Information missing. For instance, it not just runs once, it will run after the initial render and not on subsequent renders.Henandchickens

© 2022 - 2024 — McMap. All rights reserved.