DEV Community

Cover image for You’re  One Import Away From Managing Global State In React
Yezy Ilomo
Yezy Ilomo

Posted on

You’re One Import Away From Managing Global State In React

Intro

When it comes to using third party libraries in my projects, I’m a big fan of libraries which provides simple and intuitive API, the ones that just makes sense at first glance.

There’re many libraries for managing global state in ReactJS, so when it comes to choosing one, as said earlier I look for simplicity and intuitive API.

That being said I would like to present to you a simple example, in this example we’re going to use a state management library called state-pool for managing our global states.

Assuming you have a basic knowledge of ReactJS and hooks, try to take a little bit of your time to understand the code below for managing global state in a component

import React from 'react';
import { createStore } from 'state-pool';


const store = createStore();  // Create a store for storing our global state
store.setState("count", 0);  // Create and initialize "count" global state

function Counter(props){
    // Use "count" global state
    const [count, setCount] = store.useState("count");

    // Other stuff 
}
Enter fullscreen mode Exit fullscreen mode

Questions

  1. How easy was it for you to understand this example?

  2. How familiar was it, assuming you have a basic knowledge of ReactJS and hooks?

You can leave your answers to these questions to the comments section below.

Now let’s go!…

State pool is a state management library based on global variables and react hooks, it comes with a very simple and intuitive API which follows built in state management patterns in React(Hooks).

With these patterns you’re likely to be familiar with state pool without even learning it, like in a previous example above most people with a basic knowledge of ReactJS and hooks could understand how it works.

Managing global state with state-pool is very simple, all you need to do is

  1. Create a store(which is basically a container for your global state) by using createStore
  2. Create and initialize a global state by using store.setState
  3. Use your global state in your component through store.useState hook

These three steps summarises pretty much everything you need to use state-pool.

Below is an example showing how to use state-pool to manage global state

import React from 'react';
import { createStore } from 'state-pool';


const store = createStore();  // Create store for storing our global state
store.setState("count", 0);  // Create and initialize a global state

function ClicksCounter(props){
    // Use "count" global state
    const [count, setCount] = store.useState("count");

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

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

At this point you might have noticed that all you need to import from state-pool to be able to manage your global state is createStore, this is because store implements and encapsulates everything you need to manage your global state, this makes sense because a store is a container for your global states, so it should be able to manage everything in it, you just need to create one and use it.

It’s easy to understand that store.setState is used to set state in a store

Also if you’re already familiar with the built in useState hook it’s easy to understand that store.useState works the same way but it’s using the state from a store.

store.useReducer

At this point you might have guessed there’s probably something like store.useReducer that works like built in useReducer, well you’re right!…

Below is a simple example showing how to use store.useReducer hook

store.setState("user", {
    name: "Yezy",
    age: 25,
    email: "yezy@me.com"
});

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

function Component(props){
    const [name, dispatch] = store.useReducer(myReducer, "user");

    // Other stuff ...
}
Enter fullscreen mode Exit fullscreen mode

Selector & Patcher

With state pool you can subscribe to deeply nested global state or a derived state, here is an example

store.setState("user", {
    name: "Yezy",
    age: 25,
    email: "yezy@me.com"
});


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] = store.useState("user", {selector: selector, patcher: patcher});

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

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

Here selector & patcher are used for specifying a way to select deeply nested state and update it.

  • 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 is the global state and the second is the selected value. The purpose of this is to merge back the selected value to the global state once it's updated.

State persistence

State pool has a built in support for state persistence, it makes saving your global states in your preferred permanent storage very easy, all you need to do is tell state pool how to save, load, clear and remove your global state from your preferred storage by using store.persist

The way to implement these is by calling store.persist and pass them as shown below

store.persist({
    saveState: function(key, value, isInitialSet){/*your code to save state */},
    loadState: function(key){/*your code to load state */},
    removeState: function(key){/*your code to remove state */},
    clear: function(){/*your code to clear storage */}
})
Enter fullscreen mode Exit fullscreen mode

After implementing these four functions you're good to go, you won’t need to worry about calling them, state-pool will be doing that for you automatically so that you can focus on using your states.

Both store.setState, store.useState and store.useReducer accepts an optional configuration parameter, persist, this is the one which is used to tell state-pool whether to save your global state to a permanent storage or not. i.e

store.setState(
    key: String,
    initialState: Any,
    config?: {persist: Boolean}
)
Enter fullscreen mode Exit fullscreen mode
store.useState(
    key: String,
    config?: {default: Any, persist: Boolean, ...otherConfigs}
)
Enter fullscreen mode Exit fullscreen mode
store.useReducer(
    reducer: Function,
    key: String,
    config?: {default: Any, persist: Boolean, ...otherConfigs}
)
Enter fullscreen mode Exit fullscreen mode

By default the value of persist in all cases is false(which means it doesn't save global states to a permanent storage), so if you want to activate it, you have to set it to be true.

What's even better about state-pool is that you get the freedom to choose what to save in your permanent storage, so you don't need to save the whole store in your permanent storage, but if you want to save the whole store you can use PERSIST_ENTIRE_STORE configuration.

Below is an example showing how you could implement state persistance in local storage.

import { createStore } from 'state-pool';

const store = createStore();

let timerId: any = null
const DEBOUNCE_TIME = 1000  // In milliseconds

store.persist({
    PERSIST_ENTIRE_STORE: true,  // Use this only if you want to persist the entire store
    saveState: function(key, value, isInitialSet){
        const doStateSaving = () => {
            try {
                const serializedState = JSON.stringify(value);
                window.localStorage.setItem(key, serializedState);
            } catch {
                // Ignore write errors
            }
        }

        if(isInitialSet){
            // We don't debounce saving state since it's the initial set
            // so it's called only once and we need our storage to be updated
            // right away
            doStateSaving();
        }
        else {
            // Here we debounce saving state because it's the update and this function
            // is called every time the store state changes. However, it should not
            // be called too often because it triggers the expensive `JSON.stringify` operation.
            clearTimeout(timerId);
            timerId = setTimeout(doStateSaving, DEBOUNCE_TIME);
        }
    },
    loadState: function(key){
        try {
            const serializedState = window.localStorage.getItem(key);
            if (serializedState === null) {
                // No state saved
                return undefined
            }
            return JSON.parse(serializedState);
        } catch (err) {
            // Failed to load state
            return undefined
        }
    },
    removeState: function(key){
        window.localStorage.removeItem(key);
    },
    clear: function(){
        window.localStorage.clear();
    }
})
Enter fullscreen mode Exit fullscreen mode

Note: When you set PERSIST_ENTIRE_STORE = true, state-pool will be persisting all your global states to the permanent storage by default unless you explicitly specify persist = false when initializing your global state.

You can do a lot with state pool apart from few mentioned, all at the cost of importing only one thing createStore.

All you need is createStore the rest can be handled by a store itself.

Feature & Advantages

Here are some of the features and advantages of using state pool

  • Simple, familiar, flexible and very minimal core API but powerful
  • Built-in support for 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
  • Can be used outside react components
  • Support both key based and non-key based global state
  • States are stored as global variables(Can be used anywhere)
  • Doesn't wrap your app in context providers
  • Very organized API, You can do almost everything with a single import

Installing state pol

You can install state pool with
yarn add state-pool
Or
npm install state-pool

Conclusion

Congratulations for making to this point 🎉🎉,
if you want to learn more about this state management library you can check its full documentation HERE.

Live examples HERE.

Giving it a star on GitHub will be appreciated.

Lastly I would like to hear your opinions, what do you think of this library?.

Discussion (0)