ChatGPT解决这个技术问题 Extra ChatGPT

How to call loading function with React useEffect only once

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 (...);
}

T
Tholle

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>;
}

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.
yep... more about skipping is documented here: reactjs.org/docs/…
This seems like the simplest answer, but ESLint complains... see other answer on this thread stackoverflow.com/a/56767883/1550587
Just pass loadDataOnlyOnce into the dependencies array. Does that work?
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.
E
Edan Chetrit

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.


a
andromeda

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.


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.
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.
I don't quite understand... "react-hooks/exhaustive-deps" still whines about the empty array in const useMountEffect = (fun) => useEffect(fun, [])
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.
This will not "get around" the ESLint rule b/c it will still call out that useEffect has a dependency: fun.
w
wuarmin

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.

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
    }, []);
    return (...);
}

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 (...);
}

This answer deserves more attention by beginner React developers.
@JusticeBringer I would say not only for beginners. It is not a straightforward concept.
Forth option. Place the function content directly into useEffect. This also removes "eslint" error. Thanks for explaining the concept by the way.
A
Anastasia

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

{user ? user.name.first : 'Loading...'}
; } ReactDOM.render(, document.getElementById('app'));


Y
Yasin Tazeoglu
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);

Thanks! Saved my day. Ideal for calling functions once, but only after some state needs to be loaded.
This is a better solution than the accepted answer
e
ecoologic

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, [])


S
Shuhad zaman

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.


g
glinda93

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])
);

j
jb.darkcharm

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

d
drent

make sure your component isn't wrapped into <React.StrictMode /> if you are using useEffect with [] dependency


Why? What happens when it is wrapped in it?
@DávidMolnár it will called twice
It's great to point out the hassle of strict mode in this respect, though it's worth noting that the reason it runs twice is because it simulates mounting and unmounting the component, which it should be resilient to... you can't have a component that "can only be mounted once".
G
Gnopor

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

import { useEffect, useRef } from "react";

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

    useEffect(() => {
        !isRunned.current &&
            {
                /* CODE THAT SHOULD RUN ONCE */
            };

        return () => {
            isRunned.current = true;
        };
    }, []);

    return <div> content </div>;
}

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


Can you add more specifics why this is the solution for React 18?
Sorry I meant that I had this issue with React 18.
G
Greggory Wiley

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')
    };
 };

V
Vignesh

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])

https://i.stack.imgur.com/jbUES.png

https://i.stack.imgur.com/SE8Nt.png