Крючок useEffect React будет запускать переданную функцию при каждом изменении. Это можно оптимизировать, чтобы функция вызывалась только при изменении нужных свойств.

Что делать, если я хочу вызвать функцию инициализации из componentDidMount и не вызывать ее снова при изменениях? Допустим, я хочу загрузить сущность, но функции загрузки не нужны никакие данные из компонента. Как это можно сделать с помощью хука useEffect?

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

С помощью крючков это может выглядеть следующим образом:

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire on every change :(
    }, [...???]);
    return (...);
}

Ответы (10)

Если вы хотите запускать функцию, переданную useEffect, только после первоначального рендеринга, вы можете передать ей пустой массив в качестве второго аргумента.

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

  return 
{/* ... */}
; }

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



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.

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

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.

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

В случае, если вы просто вызываете функцию в useeffect после рендеринга, вы добавляете пустой массив в качестве второго аргумента для useeffect

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

вызов пользовательского хука

добавление пустого массива для рендеринга

Мне нравится определять функцию mount, она обманывает EsLint так же, как это делает useMount, и я нахожу ее более понятной.

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

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

function useOnceCall(cb, condition = true) {
  const isCalledRef = React.useRef(false);

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

и использовать его.

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

или

useOnceCall(()=>{
  console.log('Fetched Data');
}, isFetched);

Вот моя версия ответа Ясина.

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;

Использование:

useOnceEffect(
  useCallback(() => {
    nonHookFunc(deps1, deps2);
  }, [deps1, deps2])
);

оставьте массив зависимостей пустым. надеюсь, это поможет вам лучше понять.

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

пустой массив зависимостей запускается только один раз, на горе

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

передайте значение в качестве зависимости. если зависимости изменились с прошлого раза, эффект будет запущен снова.

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

Нет зависимости. Он вызывается после каждого рендеринга.

2022 WebDevInsider