Manage global state with React hooks.
Tags: react, reactnative, javascript, reactn
Published: 2019-06-05 23:06:25

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](https://github.com/CharlesStover/reactn#readme), while also targeting class components, offers React hooks 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](https://github.com/CharlesStover/reactn). To install ReactN, use `npm install reactn` 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.

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

```javascript
import { useState } from 'react';

const MyComponent = () => {
  const [ avatar, setAvatar ] = useState('anonymous.png');
  return (
    <img
      alt="Avatar"
      onClick={() => {
        const newAvatar = prompt("Enter your avatar URL:");
        setAvatar(newAvatar);
      }}
      src={avatar}
    />
  );
};
```

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. If 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](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). 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](https://github.com/charlesStover/reactn#install) with the `setGlobal` helper function. Just provide the state object, and you’re done.

```javascript
import { setGlobal } from 'reactn';

setGlobal({
  avatar: 'anonymous.png'
});
```

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 name_ 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.

```javascript
import { useGlobal } from 'reactn';

const MyComponent = () => {
  const [ avatar, setAvatar ] = useGlobal('avatar');
  return (
    <img
      alt="Avatar"
      onClick={() => {
        const newAvatar = prompt("Enter your avatar URL:");
        setAvatar(newAvatar);
      }}
      src={avatar}
    />
  );
};
```

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.

```javascript
const MyComponent = () => {
  const [ global, setGlobal ] = useGlobal();
  return (
    <img
      alt="Avatar"
      onClick={() => {
        const newAvatar = prompt("Enter your avatar URL:");
        setGlobal({
          avatar: newAvatar
        });
      }}
      src={global.avatar}
    />
  );
};
```

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.

```javascript
const MyComponent = () => {
  const [ global, setGlobal ] = useGlobal();
  if (global.x) {
    return global.x;
  }
  return global.y;
};
```

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:

```javascript
const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return initialState;
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
  }
}

function Counter() {
  const [ state, dispatch ] = useReducer(
    reducer,
    {count: initialCount}
  );
  const reset = () => dispatch({ type: 'reset' });
  const increment = () => dispatch({ type: 'increment' });
  const decrement = () => dispatch({ type: 'decrement' });
  return (
    <>
      Count: {state.count}
      <button onClick={reset}>Reset</button>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </>
  );
}
```

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.

ReactN uses `useDispatch` to handle reducers. The above example using global state would look like this:

```javascript
import { useDispatch } from 'reactn';

setGlobal({ count: 0 });

function reducer(count, action) {
  switch (action.type) {
    case 'reset':
      return 0;
    case 'increment':
      return count + 1;
    case 'decrement':
      return count - 1;
  }
}

function Counter() {

  // Subscribe to changes to count, because our view depends on it.
  const [ count ] = useGlobal('count');

  // Dispatch changes to count using our reducer function.
  const dispatch = useDispatch(reducer, 'count');

  const reset = () => dispatch({ type: 'reset' });
  const increment = () => dispatch({ type: 'increment' });
  const decrement = () => dispatch({ type: 'decrement' });
  return (
    <>
      Count: {count}
      <button onClick={reset}>Reset</button>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </>
  );
}
```

# Summary 📝

## Similarities:
* `useGlobal` and `useDispatch` are React hooks.
* `useGlobal` returns a value and a function for changing that value.
* `useDispatch` supports reducers.
* `useDispatch` returns a function for changing the current state.

## Differences:
* `useGlobal` takes an property name instead of an initial value.
* `useGlobal` can return the entire global state by not providing a parameter.
* `useDispatch` takes a property name in addition to a reducer.
* `useDispatch` does not (currently) return the value of the property, so as not to subscribe to it.
  * If your Component only updates the value, it does not need to re-render when that value changes. It may not display that value at all.

To install ReactN, use `npm install reactn` or `yarn add reactn`.

# Conclusion 🔚

Community feedback and pull requests for improving the `useGlobal` and `useDispatch` React hooks, as well as [the plethora of other global state features of the ReactN package](https://github.com/charlesStover/reactn#readme), are appreciated on [the GitHub repository](https://github.com/charlesStover/reactn).

If you liked this article, feel free to give it a heart or unicorn. 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](https://www.linkedin.com/in/charles-stover), [Medium](https://medium.com/@Charles_Stover), and [Twitter](https://twitter.com/CharlesStover), or [check out my portfolio on CharlesStover.com](https://charlesstover.com/).