Global state management in React with global variables and hooks. State management doesn't have to be so hard.
Tags: react, javascript
Published: 2020-05-27 20:24:14

# Introduction

First of all I'd like to talk a little bit about state management in react. State management in react can be divided into two parts
- Local state management 
- Global state management 

Local states are used when we're dealing with states which are not shared among two or more components(i.e they are used within a single component)

Global states are used when components need to share states.

React provides a very good and simple way to manage local state(React hooks) but when it comes to global state management the options available are overwhelming. React itself provides the context API which many third party libraries for managing global state are built on top of it, but still the APIs built are not as simple and intuitive as react state hooks, let alone the cons of using the context API to manage global state which we won't be discussing in this post, but there are plenty of articles talking about it, so check them out if you want to explore deeper.

# So what's new?

Today I want to introduce a different approach on managing global state in react which I think it might allow us to build simple and intuitive API for managing global state just like hooks API.

The concept of managing states comes from the concept of variables which is very basic in all programming languages. In managing state we have local and global states which corresponds to local and global variables in the concept of variables. In both concepts the purpose of global(state & variable) is to allow sharing it among entities which might be functions, classes, modules, components etc, while the purpose of local(state & variable) is to restrict its usage to the scope where it has been declared which might also be a function, a class, a module, a component etc.

So these two concepts have a lot in common, this made me ask my self a question 
**"What if we could be able to use global variables to store global states in react?"**.
So I decided to experiment it.

# Show me the code

I started by writing a very simple and probably a dumb example as shown below

```js
import React from 'react';

// use global variable to store global state
let count = 0;

function Counter(props){
    let incrementCount = (e) => {
        ++count;
        console.log(count);
    }

    return (
        <div>
            Count: {count}
            <br/>
            <button onClick={incrementCount}>Click</button>
        </div>
    );
}

ReactDOM.render(<Counter/>, document.querySelector("#root"));
```

As you might have guessed this example renders `count: 0` but if you click to increment, the value of `count` rendered doesn't change, but the one printed on a console changes. So why this happens despite the fact that we have only one `count` variable?.

Well this happens because when we click, the value of `count` increments(that's why it prints incremented value on a console) but the component `Counter` doesn't re-render to get the latest value of `count`. 

So that's what we are missing to be able to use our global variable `count` to store a global state. Let's try to solve this by re-rendering our component when we update our global variable. Here we are going to use `useState` hook to force our component to re-render so that it gets a new value.


```js
import React from 'react';

// use global variable to store global state
let count = 0;

function Counter(props){
    const [,setState] = useState();

    let incrementCount = (e) => {
        ++count;
        console.log(count);

        // Force component to re-render after incrementing `count`
        // This is hack but bare with me for now
        setState({});
    }

    return (
        <div>
            Count: {count}
            <br/>
            <button onClick={incrementCount}>Click</button>
        </div>
    );
}

ReactDOM.render(<Counter/>, document.querySelector("#root"));
```
So this works, it'll basically re-render every time you click. 

I know, I know this is not a good way to update a component in react but bare with me for now. We were just trying to use global variable to store global state and it just worked so let's just cerebrate this for now.


Okay now let's continue...

# What if components need to share state?

Let's first refer to the purpose of global state,

"Global states are used when components need to share states".

In our previous example we have used `count` global state in only one component, now what if we have a second component in which we would like to use `count` global state too?. 

Well let's try it

```js
import React from 'react';

// use global variable to store global state
let count = 0;

function Counter1(props){
    const [,setState] = useState();

    let incrementCount = (e) => {
        ++count;
        setState({});
    }

    return (
        <div>
            Count: {count}
            <br/>
            <button onClick={incrementCount}>Click</button>
        </div>
    );
}

function Counter2(props){
    const [,setState] = useState();

    let incrementCount = (e) => {
        ++count;
        setState({});
    }

    return (
        <div>
            Count: {count}
            <br/>
            <button onClick={incrementCount}>Click</button>
        </div>
    );
}

function Counters(props){
    return (
        <>
            <Counter1/>
            <Counter2/>
        </>
    );
}

ReactDOM.render(<Counters/>, document.querySelector("#root"));
```
Here we have two components `Counter1` & `Counter2`, they are both using `counter` global state. But when you click the button on `Counter1` it will update the value of `count` only on `Counter1`. On `counter2` it will remain 0. Now when you click the button on `Counter2` it updates but it jumps from zero to the last value on `Counter1` plus one. If you go back to the `Counter1` it does the same, jump from where it ended to the last value on `Counter2` plus one.


**Mmmmmmmh this is weird, what might be causing that?..**


Well the reason for this is, when you click the button on `Counter1` it increments the value of `count` but it re-renders only `Counter1`, since `Counter1` and `Counter2` doesn't share a method for re-rendering, each has its own `incrementCount` method which runs when the button in it is clicked. 

Now when you click `Counter2` the `incrementCount` in it runs, where it takes the value of `count` which is already incremented by `Counter1` and increment it, then re-render, that's why the value of count jumps to the last value on `Counter1` plus one. If you go back to `Counter1` the same thing happen.

So the problem here is, when one component updates a global state other components sharing that global state doesn't know, the only component which knows is the one updating that global state. As a result when the global state is updated other components which share that global state won't re-render. 


So how do we resolve this?....


It seems impossible at first but if you take a look carefully you will find a very simple solution.

Since the global state is shared, the solution to this would be to let the global state notify all the components(sharing it) that it has been updated so all of them need to re-render.

But for the global state to notify all components using it(subscribed to it), it must first keep track of all those components. 

So to simplify the process will be as follows

1. Create a global state(which is technically a global variable)

2. Subscribe a component(s) to a created global state(this lets the global state keep track of all components subscribed to it)

3. If a component wants to update a global state, it sends update request

4. When a global state receives update request, it performs the update and notify all components subscribed to it for them to update themselves(re-render)

Here is the architectural diagram for more clarification 
![Architecture Diagram](https://raw.githubusercontent.com/yezyilomo/state-pool/master/docs/images/architecture_diagram.png)

You are probably already familiar with this design pattern, it's quite popular, it's called **Observer Design Pattern**.

With this and a little help from hooks, we'll be able to manage global state completely with global variables.


Let's start by implementing our global state

```js
function GlobalState(initialValue) {
    this.value = initialValue;  // Actual value of a global state
    this.subscribers = [];     // List of subscribers

    this.getValue = function () {
        // Get the actual value of a global state
        return this.value;
    }

    this.setValue = function (newState) {
        // This is a method for updating a global state

        if (this.getValue() === newState) {
            // No new update
            return
        }

        this.value = newState;  // Update global state value
        this.subscribers.forEach(subscriber => {
            // Notify subscribers that the global state has changed
            subscriber(this.value);
        });
    }

    this.subscribe = function (itemToSubscribe) {
        // This is a function for subscribing to a global state
        if (this.subscribers.indexOf(itemToSubscribe) > -1) {
            // Already subsribed
            return
        }
        // Subscribe a component
        this.subscribers.push(itemToSubscribe);
    }

    this.unsubscribe = function (itemToUnsubscribe) {
        // This is a function for unsubscribing from a global state
        this.subscribers = this.subscribers.filter(
            subscriber => subscriber !== itemToUnsubscribe
        );
    }
}
```

From the implementation above, creating global state from now on will be as shown below

```js
const count = new GlobalState(0);
// Where 0 is the initial value
```

So we're done with global state implementation, to recap what we've done in `GlobalState`

1. We have created a mechanism to subscribe & unsubscribe from a global state through `subscribe` & `unsubscribe` methods.

2. We have created a mechanism to notify subscribers through `setValue` method when a global state is updated

3. We have created a mechanism to obtain global state value through `getValue` method


Now we need to implement a mechanism to allow our components to subscribe, unsubscribe, and get the current value from `GlobalState`.

As stated earlier, we want our API to be simple to use and intuitive just like hooks API. So we are going to make a `useState` like hook but for global state.

We are going to call it `useGlobalState`.

Its usage will be like 

```js
const [state, setState] = useGlobalState(globalState);
```

Now let's write it..

```js
import { useState, useEffect } from 'react';


function useGlobalState(globalState) {
    const [, setState] = useState();
    const state = globalState.getValue();

    function reRender(newState) {
        // This will be called when the global state changes
        setState({});
    }

    useEffect(() => {
        // Subscribe to a global state when a component mounts
        globalState.subscribe(reRender);

        return () => {
            // Unsubscribe from a global state when a component unmounts
            globalState.unsubscribe(reRender);
        }
    })

    function setState(newState) {
        // Send update request to the global state and let it 
        // update itself
        globalState.setValue(newState);
    }

    return [State, setState];
}
```

That's all we need for our hook to work. The very important part of `useGlobalState` hook is subscribing and unsubscribing from a global state. Note how `useEffect` hook is used to make sure that we clean up by unsubscribing from a global state to prevent a global state from keeping track of unmounted components.


Now let's use our hook to rewrite our example of two counters.

```js
import React from 'react';

// using our `GlobalState`
let globalCount = new GlobalState(0);

function Counter1(props){
    // using our `useGlobalState` hook
    const [count, setCount] = useGlobalState(globalCount);

    let incrementCount = (e) => {
        setCount(count + 1)
    }

    return (
        <div>
            Count: {count}
            <br/>
            <button onClick={incrementCount}>Click</button>
        </div>
    );
}

function Counter2(props){
    // using our `useGlobalState` hook
    const [count, setCount] = useGlobalState(globalCount);

    let incrementCount = (e) => {
        setCount(count + 1)
    }

    return (
        <div>
            Count: {count}
            <br/>
            <button onClick={incrementCount}>Click</button>
        </div>
    );
}

function Counters(props){
    return (
        <>
            <Counter1/>
            <Counter2/>
        </>
    );
}

ReactDOM.render(<Counters/>, document.querySelector("#root"));
```

You will notice that this example works perfectly fine. When `Counter1` updates `Counter2` get updates too and vice versa.


This means it's possible to use global variables to manage global state. As you saw, we have managed to create a very simple to use and intuitive API for managing global state, just like hooks API. We have managed to avoid using Context API at all, so no need for Providers or Consumers.

You can do a lot with this approach, things like selecting/subscribing to deeply nested global state, persisting global state to a local storage, implement key based API for managing global state, implement `useReducer` like for global state and many many more.

I myself wrote an entire library for managing global state with this approach it includes all those mentioned features, here is the link if you want to check it out https://github.com/yezyilomo/state-pool.

Thank you for making to this point, I would like to hear from you, what do you think of this approach?.