What’s the difference between useState and useEffect?

To put it simply, both useState and useEffect enhance functional components to make them do things that classes can but functional components (without hooks) cannot:

  • useState allows functional components to have state, like this.state in class components.
  • useEffect allows functional components to have lifecycle methods (such as componentDidMount, componentDidUpdate and componentWillUnmount) in one single API.

Refer to the examples below for further illustration:

useState

class CounterClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }
  
  render() {
    return <div>
      <p>Count: {this.state.count}</p>
      <button onClick={() => this.setState({ 
        count: this.state.count + 1
      })}>Increase</button>
    </div>;
  }
}

function CounterFunction() {
  const [count, setCount] = React.useState(1);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => 
        setCount(count + 1)}
      >Increase</button>
    </div>
  );
}

ReactDOM.render(
  <div>
    <CounterClass />
    <CounterFunction />
  </div>
, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>

useEffect

class LifecycleClass extends React.Component {
  componentDidMount() {
    console.log('Mounted');
  }
  
  componentWillUnmount() {
    console.log('Will unmount');
  }
  
  render() {
    return <div>Lifecycle Class</div>;
  }
}

function LifecycleFunction() {
  React.useEffect(() => {
    console.log('Mounted');
    return () => {
      console.log('Will unmount');
    };
  }, []); // Empty array means to only run once on mount.
  return (
    <div>Lifecycle Function</div>
  );
}

ReactDOM.render(
  <div>
    <LifecycleClass />
    <LifecycleFunction />
  </div>
, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>

Read more about useState and useEffect on the official React docs.


For useState()

First, we have the functional component which did not supported state, in other words, a functional component is a stateless component.

Now, with Hooks, we have the functional component but stateful. It is achieved by using useState.


For useEffect()

First, with stateless functional component, we didn't have component lifecycle hooks. In other words, whenever you want to use component lifecycle hooks, you should consider using class component.

Now, we are able to use component lifecycle hooks without using class component. It is achieved by using useEffect. In other words, now whenever we want to use component lifecycle hooks, we already have two options either using class component or using Hooks with useEffect.


UPDATE

what’s the exact difference between useState and useEffect?

In simple words, useState allows our functional components which used to be stateless become stateful. And useEffect allows our functional components leverage the component lifecycle hooks which were, in the past, only supported for class components.


useState and useEffect are part of React 16.8+ hooks ecosystem which aims at providing Functional components the same functionality which previously was available only with class based components (state / setState and component life cycle methods (ex. componentDidMount, componentDidUpdate, and componentWillUnmount)

useState() is straight forward, it lets you have state-accessors inside a functional component.

useEffect() can combine componentDidMount, componentDidUpdate, and componentWillUnmount but is tricky.

You can decipher most of what I have to discuss here from official docs for hooks. It’s easier to see hooks at work, than to reason from text.

Pre-render lifecycle

Pre-render lifecycle events equivalent to componentWillReceiveProps or getDerivedStateFromProps and componentWillMount can just be the things we do first in the functional component before returning JSX (react-node) as the function itself is equivalent to render(…) method of class based component.

We don’t need hooks handling Pre-render lifecycle events.

Post-render lifecycle

Post-render lifecycle events, those equivalent to componentDidMount, componentDidUpdate and componentDidUnmount in class based component.

We need to ****_useEffect(…)_** to handle these Post-render lifecycle events** as we can’t write the logic tied to these lifecycle events inside the main component function as these should run after the component function returns JSX (react-node) to react-dom renderer.

This means, we have lot we can do with hooks. How?

We know useEffect(fn, […watchStates]), takes in 2 arguments.

  1. fn: (required) useEffect invokes this function to run as side-effect after every render cycle based on values being tracked for changes given by the (2) argument. The function fn, could return another function that should be run as a cleanup before the effect function runs again or component un-mounts
  2. […watchValues ]: (optional) useEffect tracks values in this array has changed from the last render cycle then only effect fn is invoked. If this argument is not given, the effect will run with every render cycle.

If we don’t pass the (2) argument all-together, the effect logic in fn will be invoked after every render cycle.

If we pass (2) array with values the component needs to watch for changes, and invoke fn on change, pretty self explanatory.

The trickiest part is in using an empty array [] as the (2) argument, we can restrict side-effect logic in fn to execute only during the mounting phase as there are no changes effect hook would be watching for after subsequent render-cycles to trigger fn again.

import React, { useState, useEffect } from "react";
export default props => {
  console.log("componentWillMount");
  console.log("componentWillReceiveProps", props);
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  const [moveCount, setMoveCount] = useState(0);
  const [cross, setCross] = useState(0);
  const mouseMoveHandler = event => {
    setX(event.clientX);
    setY(event.clientY);
  };
  useEffect(() => {
    console.log("componentDidMount");
    document.addEventListener("mousemove", mouseMoveHandler);
    return () => {
      console.log("componentDidUnmount");
      document.removeEventListener("mousemove", mouseMoveHandler);
    };
  }, []); // empty-array means don't watch for any updates
  useEffect(
    () => {
      // if (componentDidUpdate & (x or y changed))
      setMoveCount(moveCount + 1);
    },
    [x, y]
  );
  useEffect(() => {
    // if componentDidUpdate
    if (x === y) {
      setCross(x);
    }
  });
  return (
    <div>
      <p style={{ color: props.color }}>
        Your mouse is at {x}, {y} position.
      </p>
      <p>Your mouse has moved {moveCount} times</p>
      <p>
        X and Y positions were last equal at {cross}, {cross}
      </p>
    </div>
  );
};

The code snippet is simple and self explanatory. You can try it out on CodePen.

One important thing to note is that if you are making a state change inside a effect, ensure you exclude the state that’s changing inside from the watch array.

For example in the second effect (one that counts the mouse movements) we only trigger it on updates on x and y, by passing [x , y] as the second argument because

  1. Its logically correct to watch for changes to x and y to register a mouse movement
  2. If we don’t exclude moveCount from being watched, this useEffect will go into an infinite cycle, as we will be updating the same value we are also watching for changes

This article is also aviable on my Medium publication. If you like the artile, or have any comments and suggestions, please clap or leave comments on Medium.