The original link www.robinwieruch.de/react-useef…

In this tutorial, you’ll learn everything there is to know about React’s useEffect Hook. Suppose we have two components: the parent uses the React useState Hook to manage state, and its children useState and modify state using a callback event handler:

import * as React from 'react'; const App = () => { const [toggle, setToggle] = React.useState(true); const handleToggle = () => { setToggle(! toggle); }; return <Toggler toggle={toggle} onToggle={handleToggle} />; }; const Toggler = ({ toggle, onToggle }) => { return ( <div> <button type="button" onClick={onToggle}> Toggle </button> {toggle && <div>Hello React</div>} </div> ); }; export default App;Copy the code

The child conditionally renders “Hello React” based on the stateful Boolean flag from the parent component. Now let’s delve into React’s useEffect Hook. Essentially, you run a side effect function whenever you want to run useEffect. It can only run when the component is mounted, when the component is rendering, or only when the component is re-rendering, and so on. We will demonstrate its use through various useEffect examples.

REACT USEEFFECT HOOK: ALWAYS

Let’s look at the first example of React’s useEffect Hook, where we pass in the side effect function as an argument:

const Toggler = ({ toggle, onToggle }) => {
  React.useEffect(() => {
    console.log('I run on every render: mount + update.');
  });
 
  return (
    <div>
      <button type="button" onClick={onToggle}>
        Toggle
      </button>
 
      {toggle && <div>Hello React</div>}
    </div>
  );
};
Copy the code

This is the most straightforward use of useEffect, where we pass only one argument — a function. This function will render on each render — meaning it runs on the first render of the component (also known as the mount or installation of the component) and each re-render of the component (also known as the update or update of the component).

REACT USEEFFECT HOOK: MOUNT

If you want to run React’s useEffect Hook only on the first rendering of the component (also only on mount), then you can pass the second argument to useEffect:

const Toggler = ({ toggle, onToggle }) => { React.useEffect(() => { console.log('I run only on the first render: mount.'); } []); return ( <div> <button type="button" onClick={onToggle}> Toggle </button> {toggle && <div>Hello React</div>} </div> ); }Copy the code

The second argument — in this case an empty array — is called a dependent array. If the dependency array is empty, the side effect function used in React’s useEffect Hook has no dependencies, which means it only runs when the component is first rendered.

REACT USEEFFECT HOOK: UPDATE

You’ve seen the dependency array for React useEffect Hook. This array can only be used to run the useEffect side effect function if a variable changes:

const Toggler = ({ toggle, onToggle }) => {
  React.useEffect(() => {
    console.log('I run only if toggle changes (and on mount).');
  }, [toggle]);
 
  return (
    <div>
      <button type="button" onClick={onToggle}>
        Toggle
      </button>
 
      {toggle && <div>Hello React</div>}
    </div>
  );
};
Copy the code

The React component’s side effects function now runs only when variables in the dependency array change. Note, however, that this function also runs on the first rendering (mount) of the component. In any case, the size of the dependent array can grow because it is, after all, an array, so you can pass in multiple variables. Let’s add the following to the component to check:

const Toggler = ({ toggle, onToggle }) => {
  const [title, setTitle] = React.useState('Hello React');
 
  React.useEffect(() => {
    console.log('I still run only if toggle changes (and on mount).');
  }, [toggle]);
 
  const handleChange = (event) => {
    setTitle(event.target.value);
  };
 
  return (
    <div>
      <input type="text" value={title} onChange={handleChange} />
 
      <button type="button" onClick={onToggle}>
        Toggle
      </button>
 
      {toggle && <div>{title}</div>}
    </div>
  );
};
Copy the code

The side effect function in React’s useEffect Hook still runs only when a variable in the dependency array changes. UseEffect does not run on this update, even though the component updates whenever we enter content in the input element. The side effect function runs for both updates only if we provide new variables in the dependency array:

const Toggler = ({ toggle, onToggle }) => {
  const [title, setTitle] = React.useState('Hello React');
 
  React.useEffect(() => {
    console.log('I run if toggle or title change (and on mount).');
  }, [toggle, title]);
 
  const handleChange = (event) => {
    setTitle(event.target.value);
  };
 
  return (
    <div>
      <input type="text" value={title} onChange={handleChange} />
 
      <button type="button" onClick={onToggle}>
        Toggle
      </button>
 
      {toggle && <div>{title}</div>}
    </div>
  );
};
Copy the code

However, in this case, you can omit the second argument to useEffect — the dependency array — entirely, because these are the only two variables that trigger an update to this component, so without the second argument, because you only rely on one props and state, the side effects are rerendered.

There are multiple use cases for having the React useEffect run on updated variables. For example, after updating a state, one might want to have a callback function based on that state change.

REACT USEEFFECT HOOK: ONLY ON UPDATE

If you’ve read the previous section carefully, you know that React’s useEffect Hook and a set of dependencies also run on the first render of a component. What if you just want to run this effect in an update? We can do this by using the React useRef Hook on instance variables:

const Toggler = ({ toggle, onToggle }) => {
  const didMount = React.useRef(false);
 
  React.useEffect(() => {
    if (didMount.current) {
      console.log('I run only if toggle changes.');
    } else {
      didMount.current = true;
    }
  }, [toggle]);
 
  return (
    <div>
      <button type="button" onClick={onToggle}>
        Toggle
      </button>
 
      {toggle && <div>Hello React</div>}
    </div>
  );
};
Copy the code

When the side effect function is first run at mount time, it simply flips the instance variables and does not run the implementation details of the side effect (console.log in this case). The actual implementation logic will only run the next time the side effect runs (the first time the component is re-rendered/updated).

REACT USEEFFECT HOOK: ONLY ONCE

As you can see, you can run the React useEffect Hook function just once by passing an empty dependency array. This only runs the function once on the first rendering of the component. What if you want to run the effect function for different situations – for example, only once when a variable is updated? Let’s take a look:

const Toggler = ({ toggle, onToggle }) => {
  const calledOnce = React.useRef(false);
 
  React.useEffect(() => {
    if (calledOnce.current) {
      return;
    }
 
    if (toggle === false) {
      console.log('I run only once if toggle is false.');
 
      calledOnce.current = true;
    }
  }, [toggle]);
 
  return (
    <div>
      <button type="button" onClick={onToggle}>
        Toggle
      </button>
 
      {toggle && <div>Hello React</div>}
    </div>
  );
};
Copy the code

As before, we implement it using instance variables in the React useRef Hook to track non-state information. Once our conditions are met, such as here when the Boolean flag is set to false, we remember that we already called the effect function and don’t call it again.

REACT USEEFFECT HOOK: CLEANUP

Sometimes you need to clear your effects from the React useEffect Hook during component rerendering. Fortunately, this is a built-in feature of useEffect, which returns a cleanup function in useEffects’ effect function. The following example shows you a timer implementation using the React useEffect Hook:

import * as React from 'react';
 
const App = () => {
  const [timer, setTimer] = React.useState(0);
 
  React.useEffect(() => {
    const interval = setInterval(() => setTimer(timer + 1), 1000);
 
    return () => clearInterval(interval);
  }, [timer]);
 
  return <div>{timer}</div>;
};
 
export default App;
Copy the code

When the component first renders, it sets an interval with React’s useEffect Hook, ticking every 1 second. As soon as the interval ticks, the timer’s state increases by one. The state change initiates the re-rendering of the component. Because the timer state has changed, if there is no clear function, the useEffect function runs again and sets another interval. This is not the behavior we want, because after all we only need an interval. This is why the useEffect function clears the interval before the component is updated, and then the component sets the new interval. Essentially, in this example, the interval runs for only one second before it is cleared.

REACT USEEFFECT HOOK: UNMOUNT

The useEffect hook cleanup function is also run when the component is unloaded. This makes sense for an interval or any other memory-consuming object that should stop running after the component no longer exists. In the useEffect example below, we replace the previous example with another version:

import * as React from 'react'; const App = () => { const [timer, setTimer] = React.useState(0); React.useEffect(() => { const interval = setInterval( () => setTimer((currentTimer) => currentTimer + 1), 1000 ); return () => clearInterval(interval); } []); return <div>{timer}</div>; }; export default App;Copy the code

Now we’re using the ability of the useState hook to update state with functions instead of values. This function takes the current timer as an argument. As a result, we no longer need to supply timers externally, and the effect can only be run once on the mount (empty dependent array). This is why the cleanup function here is only called when the component is unloaded (due to page transitions or conditional rendering).