UseEffect hook

Bartłomiej Szajewski

If you want to fully switch from classes to functional components with hooks, you need to not only get the knowledge about handling state within component, but also how to perform side effects. The answer is pretty simple, you need to learn useEffect hook!

But what are the side effects?

In programming, a side effect is a situation, when a procedure changes a variable from external scope. In React, all asynchronous operations, which may update our component, are called side effects or effects, for example:

  • fetching data
  • subscriptions
  • DOM structure updates
  • timers
  • event listeners

We can group them into:

  • effects without cleanup
  • effects with cleanup

Effects without cleanup

Sometimes, you’d like to react somehow on your component updates. Let’s create a simple code example, where we want to update the page title by number. At the very beginning, we want it to be 0. A button on the page allows the user to increment the number after it’s clicked.

If you are already familiar with React lifecycle methods, you’ve surely used componentDidMount and componentDidUpdate. A class implementation using these methods:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 0
    };
    this.onClick = this.onClick.bind(this);
  }

  componentDidMount() {
    document.title = this.state.value;
  }

  componentDidUpdate() {
    document.title = this.state.value;
  }

  onClick(e) {
    this.setState({
      value: this.state.value + 1
    });
  }

  render() {
    return <button onclick="{this.onClick}">Increment</button>;
  }
}

As you may have already noticed, we have to use the same code in two different places. What about DRY? It’s a common pattern in React. In situations, where you have to duplicate your code in two lifecycle methods simultaneously, hooks can greatly reduce the amount of lines. It’s as simple as replacing these methods with useEffect!

const App = () => {
  const [value, setValue] = useState(0);

  useEffect(() => {
    // componentDidMount/componentDidUpdate
    document.title = value;
  }, [value]); // only re-run effect if value changes

  return (
    <button value="{value}" onclick="{()" ==""> setValue(value + 1)}>
      Increment
    </button>
  );
};

By default, useEffect hook executes after each render. You can optionally optimize it, passing the second parameter, which is an array of dependencies. React will perform shallow comparison and re-render component only when it changes. In our case, we passed value, so each time it changes, React re-runs our effect.

Effects with cleanup

Imagine a situation, where you want to show the information about window width on your page. To achieve that, we need to handle window resize event. Event handling requires a careful approach. It’s important to remember about event listeners removal, the moment they’re no longer needed. Otherwise, memory leaks occur and the application might crash. From component’s lifecycle perspective, the solution is to bind event in componentDidMount and remove the binding in componentDidUnmount.

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      size: window.outerWidth
    };
    this.onWindowResize = this.onWindowResize.bind(this);
  }

  onWindowResize(e) {
    this.setState({
      size: e.target.outerWidth
    });
  }

  componentDidMount() {
    window.addEventListener("resize", this.onWindowResize);
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.onWindowResize);
  }

  render() {
    return <p>{this.state.size}</p>;
  }
}

Here you can notice that we have two opposite methods of event listener – add in componentDidMount and remove in componentWillUnmount. The problem with the solution above is that we need to separate the logic, related to one element, into two methods. How can hooks help solve that issue?

const App = () => {
  const [size, setSize] = useState(window.outerWidth);

  useEffect(() => {
    // componentDidMount
    const onWindowResize = e => setSize(e.target.outerWidth);
    window.addEventListener("resize", onWindowResize);

    return () => {
      // componentWillUnmount
      window.removeEventListener("resize", onWindowResize);
    };
  }, []);

  return <p>{size}</p>;
};

As you can see, useEffect gives us an optional cleanup mechanism. Effect may return a function that will perform cleanup when component unmounts.

Summary

I hope I convinced you enough to use hooks as often as possible!

useEffect is another hook that makes our functional components more powerful. It allows you to perform side effects like data fetching, subscription, event handling, etc. which was previously possible only in lifecycle methods within class components. What’s more, useEffect has some extra functionality. The returned function can be used to perform cleanup and keep logic in one place. Performance boost can be achieved by passing the second argument. Code readability is higher and instead of learning all lifecycle methods, just one is enough – which might be important for React beginners.

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami