Trusted answers to developer questions

React "hooking" into lifecyle methods

Free System Design Interview Course

Many candidates are rejected or down-leveled due to poor performance in their System Design Interview. Stand out in System Design Interviews and get hired in 2024 with this popular free course.

The useEffect hook is the combination of the componentDidMount, componentDidUpdate, and componentWillUnmount class lifecycle methods. This hook is the ideal place for setting up listeners, fetching data from APIs, and removing listeners before the component is removed from the DOM.

The useEffect function is like saying, “Hi React, please do this thing after you render. ALWAYS.”

Let’s look at an example of useEffect in comparison with class lifecycle methods. Normally, in a class component, we write this kind of code:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      role: 'web developer',
      name: 'Nathan',
    };
  }

  componentDidMount() {
    console.log(
      `didMount: Hello I'm ${this.state.name} and I'm a ${this.state.role}`
    );
  }

  componentDidUpdate() {
    console.log(
      `didUpdate: Hello I'm ${this.state.name} and I'm a ${this.state.role}`
    );
  }

  render() {
    return (
      <div>
        <p>{`Hello I'm ${this.state.name} and I'm a ${this.state.role}`}</p>
        <button
          onClick={() =>
            this.setState({ name: 'Gary', role: 'data scientist' })
          }
        >
          Change me
        </button>
      </div>
    );
  }
}

Since componentDidMount is only run once when the component is inserted into the DOM tree structure, subsequent renders won’t trigger the method anymore. In order to do run something on each render, you need to use componentDidUpdate method.

Since useEffect runs on every render, using useEffect hook is like having both componentDidMount and componentDidUpdate in one single method. It accepts two arguments:

  1. (mandatory): A function to run on every render.
  2. (optional): An array of state variables to watch for changes. useEffect will be skipped if none of the variables are updated.

Rewriting the above class into a functional component would look like this:

const Example = props => {
  const [name, setName] = useState('Nathan');
  const [role, setRole] = useState('web developer');

  useEffect(() => {
    console.log(`Hello I'm ${name} and I'm a ${role}`);
  });

  return (
    <div>
      <p>{`Hello I'm ${name} and I'm a ${role}`}</p>
      <button
        onClick={() => {
          setName('Gary');
          setRole('data scientist')
          }}>
        Change me
      </button>
    </div>
  )
}

The functional component we just wrote will run the function inside useEffect on each render. Now, this isn’t optimal because the state won’t be updated after the first click. This is where useEffect's second argument comes into play.

useEffect(() => {
    console.log(`Hello I'm ${name} and I'm a ${role}`);
  }, [name, role] );

By adding the array above, React will skip running the console.log method when there is no change made to the state variables.

The componentWillUnmount and skipping componentDidUpdate part

You might have some code that needs to run when the component is removed from the DOM tree. In the useEffect hook, you can specify a componentWillUnmount method by returning a function from the first argument. Here is an example:

useEffect(() => {
    console.log(`Hello I'm ${name} and I'm a ${role}`);

    return () => { console.log("componentWillUnmount"); }
  }, [name, role] );

Since componentWillUnmount is used for cleaning whatever is left behind by your component, it’s really hard to give a practical example, but one example might be when using a third-party library like C3.js from Ashley Wilson.

We can rewrite his code from this:

componentDidMount () {
  this._initGraph();
}

componentWillUnmount () {
  this.graph = this.graph.destroy();
}

to this:

useEffect(() => {
    this._initGraph();

    return () => { this.graph = this.graph.destroy(); }
  }, [] );

Do you wonder why we pass an empty array in the second argument? That’s because we want the hook to run this._initGraph() only once on componentDidMount. You can pass an empty array [] to tell React that this component should never re-render.

Conclusion

useEffect is the function component way to create lifecycle methods, and in the spirit of React hooks, it does make React code cleaner while using fewer lines of code.

RELATED TAGS

reactjs
react hooks
javascript

CONTRIBUTOR

Nathan Sebhastian
Attributions:
  1. undefined by undefined
Did you find this helpful?