React Hooks by example: useState, useCallback, useEffect, useReducer
Tags: react, tutorial
Published: 2019-07-12 22:26:34

---
title: React Hooks by example: useState, useCallback, useEffect, useReducer
description: Walk through the refactoring of a React class-based component to use useCallback, useEffect, useReducer and useState hooks.
published: true
tags: react, tutorial
canonical_url: https://kewah.com/react-hooks-by-example/
cover_image: https://images.unsplash.com/photo-1472235477785-4a74ac750010?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80
---

_Photo by [@ckirby](https://unsplash.com/@ckirby) on [Unsplash](https://unsplash.com)_

In this article, we will touch upon how to use `useCallback`, `useEffect`,`useReducer` and `useState` hooks.

We will build a component that gives the user the ability to search for a list of users. The component will store the data about the request state (if it’s loading) and response (the user list or the error information). It will listen for the form submit event and call the backend with the input’s value to get the list of users. There are different ways to achieve it, such as using Redux, but we will keep it basic since we will focus on the hooks.

## The class way (without hooks)

Using a class component, it could look like this:

```javascript
class UserSearch extends React.Component {
  constructor(props, ...rest) {
    super(props, ...rest);

    this.state = {
      loading: false,
      error: undefined,
      users: undefined,
    };
  }

  componentWillUnmount() {
    if (this.request) {
      this.request.abort();
    }
  }

  handleFormSubmit = event => {
    this.setState({ loading: true });

    this.request = superagent.get(
      `http://localhost:8080/users/${event.target.elements.username.value}`
    );
    this.request
      .then(response => {
        this.setState({
          loading: false,
          users: response.body.items,
        });
      })
      .catch(error => {
        this.setState({
          loading: false,
          error,
        });
      });
  };

  render() {
    const { loading, error, users, searchValue } = this.state;

    return (
      <form onSubmit={this.handleFormSubmit}>
        {error && <p>Error: {error.message}</p>}

        <input type="text" name="username" disabled={loading} />
        <button type="submit" disabled={loading}>
          Search
        </button>

        {loading && <p>Loading...</p>}

        {users && (
          <div>
            <h1>Result</h1>
            <ul>
              {users.map(({ id, name }) => (
                <li key={id}>{name}</li>
              ))}
            </ul>
          </div>
        )}
      </form>
    );
  }
}
```

## The functional way

We will refactor the `UserSearch` component step by step and introduce the hooks on the way.

We no longer need to use classes when we use hooks. The first step is to extract the render method into a function based component. We also inline the state and the event handlers, but currently, they don’t do anything.

```javascript
const UserSearch = () => {
  const loading = false;
  const users = undefined;
  const error = undefined;

  const handleFormSubmit = () => {
    // TODO
  };

  return (
    <form onSubmit={handleFormSubmit}>
      {error && <p>Error: {error.message}</p>}

      <input type="text" name="username" disabled={loading} />
      <button type="submit" disabled={loading}>
        Search
      </button>

      {loading && <p>Loading...</p>}

      {users && (
        <div>
          <h1>Result</h1>
          <ul>
            {users.map(({ id, name }) => (
              <li key={id}>{name}</li>
            ))}
          </ul>
        </div>
      )}
    </form>
  );
};
```

## Introducing hooks

### useState

We can use the `useState` hook to store the different states we have in our component (loading, users, error). `useState` takes the initial value as a parameter and returns a tuple of the state value and a function to update the value.

```javascript
const [value, setValue] = useState(initialValue);
```

Let’s update our states using `setState`. Currently, we only initialize the states, but we need to implement the logic.

```javascript
const UserSearch = () => {
  const [loading, setLoading] = userState(false);
  const [users, setUsers] = useState();
  const [error, setError] = useState();

  const handleFormSubmit = () => {
    // TODO
  };

  return (
    <form onSubmit={handleFormSubmit}>
      {error && <p>Error: {error.message}</p>}

      <input type="text" name="username" disabled={loading} />
      <button type="submit" disabled={loading}>
        Search
      </button>

      {loading && <p>Loading...</p>}

      {users && (
        <div>
          <h1>Result</h1>
          <ul>
            {users.map(({ id, name }) => (
              <li key={id}>{name}</li>
            ))}
          </ul>
        </div>
      )}
    </form>
  );
```

### useCallback

A function-based component doesn’t have lifecycles and React calls the function for each new render, which means that for each re-render every hoisted object will be recreated. For instance, a new `handleFormSubmit` function is created every time. One of the issues is that it invalidates the tree because`<form onSubmit={handleFormSubmit}>` is different between renders (previous`handleFormSubmit` ≠ next `handleFormSubmit` because `() => {} !== () => {}`).

That’s where `useCallback` comes into play. It caches the function and creates a new one only if a dependency changes. A dependency is a value that is created in the component but is outside the `useCallback` scope.

```javascript
const fn = useCallback(() => {}, [dependencies]);
```

In the [documentation](https://reactjs.org/docs/hooks-reference.html#usecallback), they recommend “every value referenced inside the callback should also appear in the dependencies array.” Although, you may omit `dispatch` (from `useReducer`),`setState`, and `useRef` container values from the dependencies because React guarantees them to be static. However, it doesn’t hurt to specify them. Note that If we pass an empty array for the dependencies, it will always return the same function.

I recommend you to use [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) to help you to know which values we need to include in the dependencies.

You should also check the article written by Kent C. Dodds about [when to use `useCallback`](https://kentcdodds.com/blog/usememo-and-usecallback) since it also comes with a performance cost to use it over an inline callback. Spoiler: for referential equality and dependencies lists.

So, if we follow how it was done with the class, we could execute the `GET`request directly in the `useCallback`.

```javascript
const UserSearch = () => {
  const [loading, setLoading] = userState(false);
  const [users, setUsers] = useState();
  const [error, setError] = useState();

  const handleFormSubmit = useCallback(
    event => {
      event.preventDefault();

      setLoading(true);

      const request = superagent.get(
        `http://localhost:8080/users/${event.target.elements.username.value}`
      );
      request
        .then(response => {
          setLoading(false);
          setUsers(response.body.items);
        })
        .catch(error => {
          setLoading(false);
          setError(error);
        });
    },
    [setLoading, setUsers, setError]
  );

  return (
    <form onSubmit={handleFormSubmit}>
      {error && <p>Error: {error.message}</p>}

      <input type="text" name="username" disabled={loading} />
      <button type="submit" disabled={loading}>
        Search
      </button>

      {loading && <p>Loading...</p>}

      {users && (
        <div>
          <h1>Result</h1>
          <ul>
            {users.map(({ id, name }) => (
              <li key={id}>{name}</li>
            ))}
          </ul>
        </div>
      )}
    </form>
  );
};
```

⚠️ It works, there are few issues by doing that. When React unmounts the component, nothing aborts the request the same way we did in`componentWillUnmount`. Also, since the request is pending React keeps a reference to an unmounted component. So, it wastes browser resources for something the user will never interact with.

### useEffect

`useEffect` brings the lifecycle to a function based component. It is the combination of `componentDidMount`, `componentDidUpdate`, and`componentWillUnmount`. The callback of `useEffect` is executed when a dependency is updated. So, the first time the component is rendered, `useEffect`will be executed. In our case, we want to start the request when the search value is updated (on form submit). We will introduce a new state `searchValue`that is updated in the `handleFormSubmit` handler and we will use that state as a dependency to the hook. Therefore when `searchValue` is updated the`useEffect` hook will also be executed.

Finally, the `useEffect` callback must return a function that is used to clean up, for us this is where we will abort the request.

```javascript
const UserSearch = () => {
  const [loading, setLoading] = userState(false);
  const [users, setUsers] = useState();
  const [error, setError] = useState();
  const [searchValue, setSearchValue] = useState();

  const handleFormSubmit = useCallback(
    event => {
      event.preventDefault();
      setSearchValue(event.target.elements.username.value);
    },
    [setSearchValue]
  );

  useEffect(() => {
    let request;

    if (searchValue) {
      setLoading(true);

      request = superagent.get(
        `http://localhost:8080/users/${event.target.elements.username.value}`
      );
      request
        .then(response => {
          setError(undefined);
          setLoading(false);
          setUsers(response.body.items);
        })
        .catch(error => {
          setLoading(false);
          setError(error);
        });
    }

    return () => {
      if (request) {
        request.abort();
      }
    };
  }, [searchValue, setLoading, setUsers, setError]);

  return (
    <form onSubmit={handleFormSubmit}>
      {error && <p>Error: {error.message}</p>}

      <input type="text" name="username" disabled={loading} />
      <button type="submit" disabled={loading}>
        Search
      </button>

      {loading && <p>Loading...</p>}

      {users && (
        <div>
          <h1>Result</h1>
          <ul>
            {users.map(({ id, name }) => (
              <li key={id}>{name}</li>
            ))}
          </ul>
        </div>
      )}
    </form>
  );
};
```

Dan Abramov has written an excellent blog post about `useEffect` hooks: [a complete guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect/).

### useReducer

We have now a working version of our component using React Hooks 🎉. One thing we could improve is when we have to keep track of several states, such as in the request’s response we update three states. In our example, I think it’s fine to go with the current version. However, in the case we need to add more states,`useReducer` would be a better suit. That allows us to gather related states in the same area of our code and have one way to update the states.

`useReducer` expects a reducer function (that function takes an action and returns a new state) and the initial state. Similar to `useState` it returns a tuple that contains the state and the dispatch function that we use to dispatch actions.

```javascript
const [state, dispatch] = useReducer(reducer, initialState);
```

```javascript
const initialState = {
  loading: false,
  users: undefined,
  error: undefined,
  searchValue: undefined,
};

const SET_SEARCH_VALUE = 'SET_SEARCH_VALUE';
const FETCH_INIT = 'FETCH_INIT';
const FETCH_SUCCESS = 'FETCH_SUCCESS';
const ERROR = 'ERROR';

const reducer = (state, { type, payload }) => {
  switch (type) {
    case SET_SEARCH_VALUE:
      return {
        ...state,
        searchValue: payload,
      };

    case FETCH_INIT:
      return {
        ...state,
        error: undefined,
        loading: true,
      };

    case FETCH_SUCCESS:
      return {
        ...state,
        loading: false,
        error: undefined,
        result: payload,
      };

    case ERROR:
      return {
        ...state,
        loading: false,
        error: payload,
      };

    default:
      throw new Error(`Action type ${type} unknown`);
  }
};

const UserSearch = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const handleFormSubmit = useCallback(
    event => {
      event.preventDefault();

      dispatch({
        type: SET_SEARCH_VALUE,
        payload: event.target.elements.username.value,
      });
    },
    [dispatch]
  );

  useEffect(() => {
    let request;

    if (state.searchValue) {
      // highlight-next-line
      dispatch({ type: FETCH_INIT });

      request = superagent.get(
        `http://localhost:8080/users/${state.searchValue}`
      );
      request
        .then(response => {
          // highlight-next-line
          dispatch({ type: FETCH_SUCCESS, payload: response.body.items });
        })
        .catch(error => {
          // highlight-next-line
          dispatch({ type: ERROR, payload: error });
        });
    }

    return () => {
      if (request) {
        request.abort();
      }
    };
  }, [state.searchValue, dispatch]);

  return (
    <form onSubmit={handleFormSubmit}>
      {state.error && <p>Error: {state.error.message}</p>}

      <input type="text" name="username" disabled={state.loading} />
      <button type="submit" disabled={state.loading}>
        Search
      </button>

      {state.loading && <p>Loading...</p>}

      {state.users && (
        <div>
          <h1>Result</h1>
          <ul>
            {state.users.map(({ id, name }) => (
              <li key={id}>{name}</li>
            ))}
          </ul>
        </div>
      )}
    </form>
  );
};
```


As mentioned before, the benefits are not directly apparent since we don’t have that many states to handle in our example. There is more boilerplate than the `useState` version, but all states related to calling the API are managed in the reducer function.