You Can Definitely Use Global Variables To Manage Global State In React
Tags: react, javascript, webdev, showdev
Published: 2021-07-07 20:00:47

## Introduction
React provides a very good and simple way to manage local states through state hooks but when it comes to global states 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 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 managing global states in react is still a problem with no clear solution yet.

But what if I tell you there might be a solution which is based on global variables?

Yes the global variables that you use every day in your code.

[]()
[]()
## How is it possible?
The concept of managing states is very similar to the concept of variables which is very basic in almost all programming languages.

In state management we have local and global states which corresponds to local and global variables in a concept of variables. 

In both concepts the purpose of global(state & variable) is to allow sharing of a value 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 myself a question

**"What if we use global variables to store global states in react?".**


[]()
[]()
## Answers
As of now we can use a normal global variable to store a global state but the problem comes when we want to update it.

If we use regular global variable to store react global state we won't be able to get the latest value of our state right away when it gets updated, because there's no way for react to know if a global variable has changed for it to re-render all components depending on such global variable in order for them to get a fresh(updated) value. Below is an example showing this problem

```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` initially 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 the button is clicked, 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 this is the only problem standing in our way to use global variables to manage global state in react.


[]()
[]()
## Solution
Since global states are shared among components, the solution to our problem would be to let a global state notify all the components which depend on it that it has been updated so that all of them re-render to get a fresh value.

But for the global state to notify all components using it(subscribed to it), it must first keep track of 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) to get a fresh value

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

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

Luckily we won't need to implement this on ourselves because **State Pool** got our back.


[]()
[]()
## Introducing **[State Pool](https://github.com/yezyilomo/state-pool)✨🎉**.

**[State Pool](https://github.com/yezyilomo/state-pool)** is a react state management library based on global variables and react hooks. Its API is as simple and intuitive as react state hooks, so if you have ever used react state hooks(`useState` or `useReducer`) you will feel so familiar using **state-pool**. You could say **state-pool** is a global version of react state hooks.

Features and advantages of using **State Pool**
- Simple, familiar and very minimal core API but powerful
- Built-in state persistence
- Very easy to learn because its API is very similar to react state hook's API
- Support selecting deeply nested state
- Support creating global state dynamically
- Support both key based and non-key based global state
- States are stored as global variables(Can be used anywhere)


[]()
[]()
## Installing 
`yarn add state-pool`

Or

`npm install state-pool`


[]()
[]()
## Getting Started
Now let's see a simple example of how to use **state-pool** to manage global state 

```js
import React from 'react';
import {store, useGlobalState} from 'state-pool';


store.setState("count", 0);

function ClicksCounter(props){
    const [count, setCount] = useGlobalState("count");

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

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

ReactDOM.render(ClicksCounter, document.querySelector("#root"));
```

If you've ever used `useState` react hook the above example should be very familiar,

Let's break it down 

- On a 2nd line we're importing `store` and `useGlobalState` from `state-pool`.

- We are going to use `store` to keep our global states, so `store` is simply a container for global states.

- We are also going to use `useGlobalState` to hook in global states into our components.

- On a 3rd line `store.setState("count", 0)` is used to create a global state named "count" and assign 0 as its initial value.

- On 5th line `const [count, setCount] = useGlobalState("count")` is used to hook in the global state named "count"(The one we've created on 3rd line) into `ClicksCounter` component.

As you can see `useGlobalState` is very similar to `useState` in so many ways.


[]()
[]()
## Updating Nested Global State
**State Pool** is shipped with a very good way to handle global state update in addition to `setState` especially when you are dealing with nested global states.

Let's see an example with nested global state

```js
import React from 'react';
import {store, useGlobalState} from 'state-pool';


store.setState("user", {name: "Yezy", age: 25});

function UserInfo(props){
    const [user, setUser, updateUser] = useGlobalState("user");

    let updateName = (e) => {
        updateUser(function(user){
            user.name = e.target.value;
        });
    }

    return (
        <div>
            Name: {user.name}
            <br/>
            <input type="text" value={user.name} onChange={updateName}/>
        </div>
    );
}

ReactDOM.render(UserInfo, document.querySelector("#root"));
```

In this example everything is the same as in the previous example

On a 3rd line we're creating a global state named "user" and set `{name: "Yezy", age: 25}` as its initial value.

On 5th line we're using `useGlobalState` to hook in the global state named "user"(The one we've created on a 3rd line) into `UserInfo` component.

However here we have one more function returned in addition to `setUser` which is `updateUser`, This function is used for updating user object rather than setting it, though you can use it to set user object too.

So here `updateUser` is used to update user object, it's a higher order function which accepts another function for updating user as an argument(this another functions takes user(old state) as the argument).

So to update any nested value on user you can simply do 

```js
updateUser(function(user){
    user.name = "Yezy Ilomo";
    user.age = 26;
})
```

You can also return new state instead of changing it i.e

```js
updateUser(function(user){
    return {
        name: "Yezy Ilomo",
        age: 26
    }
})
```

So the array returned by `useGlobalState` is in this form `[state, setState, updateState]`
- `state` hold the value for a global state
- `setState` is used for setting global state
- `updateState` is used for updating global state


[]()
[]()
## Selecting Nested State
Sometimes you might have a nested global state but some components need to use part of it(nested or derived value and not the whole global state).

For example in the previous example we had a global state named "user" with the value `{name: "Yezy", age: 25}` but in a component `UserInfo` we only used/needed `user.name`. 

With the approach we've used previously the component `UserInfo` will be re-rendering even if `user.age` changes which is not good for performance.

**State Pool** allows us to select and subscribe to nested or derived states to avoid unnecessary re-renders of components which depends on that nested or derived part of a global state.

Below is an example showing how to select nested global state.

```js
import React from 'react';
import {store, useGlobalState} from 'state-pool';


store.setState("user", {name: "Yezy", age: 25});

function UserInfo(props){
    const selector = (user) => user.name;  // Subscribe to user.name only
    const patcher = (user, name) => {user.name = name};  // Update user.name

    const [name, setName] = useGlobalState("user", {selector: selector, patcher: patcher});

    let handleNameChange = (e) => {
        setName(e.target.value);
    }

    return (
        <div>
            Name: {name}
            <br/>
            <input type="text" value={name} onChange={handleNameChange}/>
        </div>
    );
}

ReactDOM.render(UserInfo, document.querySelector("#root"));
```

By now from an example above everything should be familiar except for the part where we pass `selector` and `patcher` to `useGlobalState` hook.

To make it clear, `useGlobalState` accept a second optional argument which is the configuration object. `selector` and `patcher` are among of configurations available.
- `selector`: should be a function which takes one parameter which is the global state and returns a selected value. The purpose of this is to subscribe to a deeply nested state.

- `patcher`: should be a function which takes two parameters, the first one is a global state and the second one is the selected value. The purpose of this is to merge back the selected value to the global state once it's updated.

So now even if `user.age` changes, the component `UserInfo` won't re-render because it only depend on `user.name`


[]()
[]()
## Creating Global State Dynamically
**State Pool** allows creating global state dynamically, this comes in handy if the name or value of a global state depend on a certain parameter within a component(it could be server data or something else).

As stated earlier `useGlobalState` accepts a second optional parameter which is a configuration object, `default` is one of available configurations.

`default` configuration is used to specify the default value if you want `useGlobalState` to create a global state if it doesn't find the one for the key specified in the first argument. For example 

```js
const [user, setUser, updateUser] = useGlobalState("user", {default: null});
```
This piece of code means get the global state for the key "user" if it's not available in a store, create one and assign it the value `null`.

This piece of code will work even if you have not created the global state named "user", it will just create one if it doesn't find it and assign it the default value `null` as you have specified.


[]()
[]()
## useGlobalStateReducer
`useGlobalStateReducer` works just like `useReducer` hook but it accepts a reducer and a global state or key(name) for the global state. In addition to the two parameters mentioned it also accepts other optional parameter which is the configuration object, just like in `useGlobalState` available configurations are `selector`, `patcher`, `default` and `persist`(This will be discussed later). For example if you have a store setup like

```js
const user = {
    name: "Yezy",
    age: 25,
    email: "yezy@me.com"
}

store.setState("user": user);
```
You could use `useGlobalStateReducer` hook to get global state in a functional component like

```js
function myReducer(state, action){
    // This could be any reducer
    // Do whatever you want to do here
    return newState;
}

const [name, dispatch] = useGlobalStateReducer(myReducer, "user");
```
As you can see, everthing here works just like in `useReducer` hook, so if you know `useReducer` this should be familiar.

Below is the signature for `useGlobalStateReducer`
```js
useGlobalStateReducer(reducer: Function, globalState|key: GlobalState|String, {default: Any, persist: Boolean, selector: Function, patcher: Function})
```


[]()
[]()
## State Persistance
Sometimes you might want to save your global states in local storage probably because you might not want to lose them when the application is closed(i.e you want to retain them when the application starts).

**State Pool** makes it very easy to save your global states in local storage, all you need to do is use `persist` configuration to tell **state-pool** to save your global state in local storage when creating your global state.

No need to worry about updating or loading your global states, **state-pool** has already handled that for you so that you can focus on using your states.

`store.setState` accept a third optional parameter which is the configuration object, `persist` is a configuration which is used to tell **state-pool** whether to save your state in local storage or not. i.e

```js
store.setState(key: String, initialState: Any, {persist: Boolean})
```
Since **state-pool** allows you to create global state dynamically, it also allows you to save those newly created states in local storage if you want, that's why both `useGlobalState` and `useGlobalStateReducer` accepts persist configuration too which just like in `store.setState` it's used to tell **state-pool** whether to save your newly created state in local storage or not. i.e

```js
useGlobalState(key: String, {defaultValue: Any, persist: Boolean})
```
```js
useGlobalStateReducer(reducer: Function, key: String, {defaultValue: Any, persist: Boolean})
```

By default the value of `persist` in all cases is `false`(which means it doesn't save global states to the local storage), so if you want to activate it, set it to be `true`. What's even better about **state-pool** is that you get the freedom to choose what to save in local storage and what's not to, so you don't need to save the whole store in local storage.

When storing state to local storage, `localStorage.setItem` should not be called too often because it triggers the expensive `JSON.stringify` operation to serialize global state in order to save it to the local storage.

Knowing this **state-pool** comes with `store.LOCAL_STORAGE_UPDATE_DEBOUNCE_TIME` which is the variable used to set debounce time for updating state to the local storage when global state changes. The default value is 1000 ms which is equal to 1 second. You can set your values if you don't want to use the default one.


[]()
[]()
## Non-Key Based Global State
**State Pool** doesn't force you to use key based global states, if you don't want to use `store` to keep your global states the choice is yours

Below are examples showing how to use non-key based global states

```js
// Example 1.
import React from 'react';
import {createGlobalState, useGlobalState} from 'state-pool';


let count = createGlobalState(0);

function ClicksCounter(props){
    const [count, setCount, updateCount] = useGlobalState(count);

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

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

ReactDOM.render(ClicksCounter, document.querySelector("#root"));
```

[]()
[]()

```js
// Example 2
const initialGlobalState = {
    name: "Yezy",
    age: 25,
    email: "yezy@me.com"
}

let user = createGlobalState(initialGlobalState);


function UserName(props){
    const selector = (user) => user.name;  // Subscribe to user.name only
    const patcher = (user, name) => {user.name = name};  // Update user.name

    const [name, setName, updateName] = useGlobalState(user, {selector: selector, patcher: patcher});

    let handleNameChange = (e) => {
        setName(e.target.value);
        // updateName(name => e.target.value);  You can do this if you like to use `updatName`
    }

    return (
        <div>
            Name: {name}
            <br/>
            <input type="text" value={name} onChange={handleNameChange}/>
        </div>
    );
}
```


[]()
[]()
## Conclusion
Thank you for making to this point, I would like to hear from you, what do you think of this approach?. 

If you liked the library give it a star at [https://github.com/yezyilomo/state-pool](https://github.com/yezyilomo/state-pool).