"There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies" -- C.A.R. Hoare, 1980 ACM Turing Award Lecture


Introduction of Hooks

The introduction of React hooks has certainly changed the perspective on state management.

Before we had this feature, it was difficult to share state logic between components. Now it’s as easy as making an abstract hook for it (example: useUserLogin).

This raises the question, why we still need state management frameworks.


What Changed?

So how did we define state before Hooks? Basically, there were two options: defining a local state in the component, or using a state management framework to set it as a global state (e.g. MobX / Redux).

Local state (before hooks):

export class UserList extends Component {
  state = {
    isLoading: false,
    hasError: false,
    users: [],
  };

  searchUsers(value) {
    this.setState({ isLoading: true });

    api
      .get(`/users?searchKey=${value}`)
      .then((data) => this.setState({ users: data }))
      .catch(() => this.setState({ hasError: true }))
      .finally(() => this.setState({ loading: false }));
  }

  render() {
    if (this.state.isLoading) {
      // render loading spinner
    }

    if (this.state.hasError) {
      // render error message
    }

    return (
      <div>
        <input onChange={(event) => this.searchUsers(event.target.value)} />
      </div>
    );
  }
}

The issue with only having these two options is best described in the following scenario. Let’s say our state does not have to be global, but we may want to reuse how we define the local state in multiple components.

In the example above, we may want to reuse setting the loading and error state — before Hooks this was not possible. The only option we had was to make it reusable with Redux. In Redux each component which wanted to search users could simply dispatch an action (searchUsers()) and listen for the global state to change.

However using this global state (Redux/MobX) results in a few problems:

  • More boilerplate code
  • Complicated flow
  • Multiple components are manipulating the global state, which may cause unwanted side effects

The solution: React Hooks!

Thankfully React introduced hooks in React 16.8. From that day onwards, it was possible to share state logic between multiple components.

In the example below, we are now able to share the loading and error behavior:

import { useState } from "react";

const useRequestHandler = () => {
  const [isLoading, setLoading] = useState(false);
  const [hasError, setError] = useState(false);
  const [data, setData] = useState(null);

  const handleRequest = (request) => {
    setLoading(true);
    setError(false);

    return api
      .get(request)
      .then(setData)
      .catch(() => setError(true))
      .finally(() => setLoading(false));
  };

  return { isLoading, hasError, data, handleRequest };
};

const UserList = () => {
  const { data, isLoading, hasError, handleRequest } = useRequestHandler();

  const searchUsers = (value) => handleRequest(`/users?searchKey=${value}`);

  return (
    <React.Fragment>
      {data.map((u) => (
        <p>{u.name}</p>
      ))}
    </React.Fragment>
  );
};

We could also make a useUserSearch if multiple components wanted to offer the functionality to search through the list of users.

However, hooks are no silver bullet. Keeping state in a hook does not mean it becomes a singleton, the state is only bound to one component. There are certain uses where we might only want to keep one instance of state (for example, fetching user info only once). This is where a state management framework proves its value.


How to Decide Where to Keep State

Now that it’s possible to share state logic across components, how do we decide between keeping state in components (local) or in a state management framework (global)?

Below is an image of my decision-making process:


What’s the Benefit of a State Management Framework?

Now we know when to decide between global and local state. But why would we still use a state management framework? What’s the benefit above using React Hooks?

Here’s a list of benefits:

  • Globally defined, which means only one instance of the data
  • Only fetching remote data once
  • Extensive dev tools
  • Provides a standardised way of working for software engineers

Conclusion

We’ve seen that React Hooks has shaken up the whole state management landscape. Since their introduction, it’s been easier to share state logic between components.

However, Hooks are no silver bullet and we might still need state management frameworks. That doesn’t mean that we need to keep every state globally — most of the time it’s better to keep it at a component level. Moving state to the state management framework should only be done when absolutely necessary.