Manage global state with React Hooks

Since the announcement of experimental Hooks in React 16.7, they have taken the React community by storm.

Unfortunately, the same way class components only manage local state, the built-in useState React hook only manages local state in functional components. Global state management is still left to higher-order components and community-contributed endeavors.

The reactn package, while also targeting class components, offers a React hook for accessing and managing global state in functional components. The ReactN package intends to integrate global state into React as if it were native functionality. In contrast to libraries like MobX and Redux, which are state-first solutions to state management, ReactN aims to be a React-first solution to global state management.

To read more about or contribute to the ReactN project, the GitHub repository is welcoming to the community. To install ReactN, use npm install reactn --save or yarn add reactn.

A useState Overview 🏁

Analogous to the built-in React hook useState, the useGlobal hook of reactn behaves as similar as possible, with a few key differences. To clearly identify these differences, I’ll first provide useState’s behavior.

The useState function takes a default value and returns a 2-item array, where the first item is the state value and the second item is a function that updates that state value.

const [ value, setValue ] = useState(DEFAULT_VALUE);

In the above example, MyComponent renders an image anonymous.png (because that is the default value of the state). When you click the image, you are prompted for a new avatar URL. The functional component’s state updates with this new URL, and it re-renders (due to state change), displaying the image you entered instead.

This works great if you want to track the avatar only in this component. But what if you have multiple components that display the user’s avatar? Or multiple instances of this same component? Each instance of MyComponent will have its own instance of state, meaning each instance of MyComponent can have a different state. In many cases like these, developers opt for a global state instead — insuring that all components are in sync with each other. In one component updates the user’s avatar, then all other components displaying the user’s avatar need to update too.

Global State Differences 🆚

An important distinction when dealing with global state is how nonsensical it is to have a default value when instantiating the state. If every component that relied on the user’s avatar had to have a default value, then the value isn’t really global: The components would not be in sync with each other, because each one would have its own, different value. You can give them each the same default value, but at that point you are not using DRY code. Every time you want to change the default value, you have to go through the effort of changing it on every component. Not only is that a huge annoyance, but it opens you up for error when one of the components coincidentally is forgotten about during the change.

Because of this, global state is typically instantiated outside of the components that use it. If the global state is given a value up front, then the components don’t need to provide a default value in case one does not already exist — it does already exist.

Instantiating the Global State 🌞

With ReactN, you can instantiate the global state with the setGlobal helper function. Just provide the state object, and you’re done.

It is recommended that this occur before ReactDOM.render, because you typically want the state to exist before any components attempt to mount.

Using the Global State 🌎

As mentioned, using the global state is meant to be as straightforward as using the local state. It is a React hook, prefixed with use, placed at the top of your functional component, that returns a 2-item array where the first item is the state value and the second item is a function that updates the state value. Since the default value is instantiated elsewhere, you do not pass the default value as a parameter to the global state hook; instead, it receives the property of the global state that you want to access. The global state is an object of many different values that you may want to manage throughout your entire application, not a single value. In the instantiation example, we created an avatar property, so we will access it here.

That’s it. We changed useState to useGlobal and we replaced the default state value with the property we wanted to access. Whenever the global property avatar is updated by any component, all components using useGlobal('avatar') will re-render with the new value.

Can I access the entire global state? 👪

Yes! If you do not provide a property to useGlobal, it will return the entire global state for you to use as you please.

The same as when you provide a specific property, your component will only re-render if you access a property, not any time the global state is updated. This may be useful if you want to conditionally subscribe to certain properties. Your component will only re-render if it accesses global.property instead of every time global.property updates.

In the above example, if global.x is truthy, your component will only re-render when the x property of the global state updates, not when the y property of the global state updates. This is because the y property of the global state does not impact the render of your component at all!

If the x property is falsey, your component will update whenever either x or y update. This is because both x and y changes will impact the render of your component.

The “magic” here is simply that your component re-renders when there is a change in a global state property that your component has accessed. Above, if x is truthy, the y property is never accessed. The component returns before ever getting the chance. If x is falsey, the y property is accessed.

If you were to useGlobal('x') and useGlobal('y'), you would be accessing both the x and y properties — even if you were to ignore y. As a result, your component would update when the unused y property is changed.

What about Reducers? 🤔

React 16.7 introduced a beautiful hook alongside useState known as useReducer. The useReducer hook allows you to pass a reducer function and initial state. It returns the state and a dispatch function. Unlike setState returned by useState, the dispatch function passes your arguments along to the reducer function.

Here is the reducer demonstrated by the React documentation:

In the above example, useReducer returns the state, which defaults to { count: 0 }, and a function that passes your parameters on to the reducer. The reducer takes the current state and your parameters to determine what the new state should be. Since actions such as { type: 'increment' } depend on the current state, the reducer returns the current state plus one.

Unlike useState, which takes the default state as a parameter (and a state can be a function), useGlobal takes a string parameter that represents the key of the global state object. Since an object key cannot be a function, useGlobal knows that you want a reducer when you give it a function. The above example using global state would look like this:

Other than setting the initial value outside the component instance, it’s exactly the same!

Summary 📝

Similarities:

  • useGlobal is a React hook.
  • useGlobal supports reducers.
  • useGlobal returns the current state and a function for changing the current state.

Differences:

  • useGlobal takes an object key instead of an initial value.
  • useGlobal supports both state and reducers with a single hook.
  • useGlobal can return the entire global state by not providing a parameter.

To install ReactN, use npm install --save or yarn add reactn.

Conclusion 🔚

Community feedback and pull requests for improving the useGlobal React hook, as well as the plethora of other global state features of the ReactN package, are appreciated on the GitHub repository.

If you liked this article, feel free to give it a clap or two. It’s quick, it’s easy, and it’s free! If you have any questions or relevant great advice, please leave them in the comments below.

To read more of my columns, you may follow me on LinkedIn and Twitter, or check out my portfolio on CharlesStover.com.