You’re One Import Away From Managing Global State In React
Yezy Ilomo
Posted on March 8, 2022
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
}
Questions
How easy was it for you to understand this example?
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
- Create a store(which is basically a container for your global state) by using
createStore
- Create and initialize a global state by using
store.setState
- 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>
);
}
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 ...
}
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>
);
}
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 */}
})
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}
)
store.useState(
key: String,
config?: {default: Any, persist: Boolean, ...otherConfigs}
)
store.useReducer(
reducer: Function,
key: String,
config?: {default: Any, persist: Boolean, ...otherConfigs}
)
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();
}
})
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?.
Posted on March 8, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 9, 2021