Introduction
We have a lot of state managers in our Javascript-World and use them every day, but now it's a time to understand "How they work".
There's a very nice state manager Effector.js which is very simple to use in your application, also it's easy to replace your current global state manager. So, I recommend to use it for your project and doesn't matter for which framework. I use it on my React Apps.
Let's start to create own Effector-like state manager!
We're gonna create basics looks like Effector, of course Effector is deeper and complex and our state manager is just simple version of it.
1) Firstly, let's create a js file (effector-clone.js
) with our state manager. We start with createStore
function that create our store instance with events.
export const createStore = initState => {
let state = initState; // State of instance
let events = new Map(); // Events subscribed to the current store
let watchers = []; // Array of watcher that get called on the state changes
let store = {
getState: () => state, // Get current state of the store
on(event, cb) {}, // Method to subscribe event
dispatch(event, payload) {}, // Dispatch event to make changes in the store
watch(cb) {} // Subscribe to the state changes
};
return store;
};
2) We need to add a function that creates event instance.
Let's add this code into the file above!
export const createEvent = () => {
// Return function that iterates stores linked to the event
let event = payload => {
event.stores.forEach((store) => {
store.dispatch(event, payload);
});
};
// Let's link stores to the event
// We're gonna call stores' dispatches on event's call
event.stores = [];
return event;
};
3) Implement on, dispatch and watch methods for the store instance.
export const createStore = initState => {
let state = initState;
let events = new Map();
let watchers = [];
let store = {
getState: () => state,
on(event, cb) {
// Subscribe to store
// We use event instance as key for map and callback as a value
// [event: Function]: callback: Function
// Set event in the events map if it hasn't the event in this store
if (!events.has(event)) {
events.set(event, cb);
event.stores.push(this);
}
return this;
},
dispatch(event, payload) {
// We get and call event's callback and
// set it's result to the store's state
const cb = events.get(event);
if (cb && typeof cb === "function") {
let newState = cb(state, payload);
// Check if state is the same
if (newState !== state) {
state = newState;
}
}
// Iterable callbacks on the state changes
watchers.forEach((watch) => watch(state, payload));
},
watch(cb) {
watchers.push(cb);
// Return function to unsubscribe the watcher
return () => {
watchers = watchers.filter((i) => i !== cb);
};
}
};
return store;
};
Core part of our state manager is Done! ✅
Use it with React + hooks ⚛︎
We're gonna use it as a global state manager. It's also okay to use inside your component.
1) Create useStore.js
file and add this simple code.
import { useEffect, useState } from "react";
export const useStore = store => {
// We get initial state of the store
const [state, setState] = useState(store.getState());
useEffect(() => {
// Pass setState function as a callback
// store.watch() returns unsubscribe function
const unsubscribe = store.watch(setState);
return () => {
// Unsubscribe our watcher on component unmount
unsubscribe();
};
}, [store]);
return state;
};
2) Create counterStore.js
file with our counter store
import { createStore, createEvent } from "./effector-clone";
export const $counter = createStore(0);
export const inc = createEvent();
export const dec = createEvent();
export const reset = createEvent();
$counter
.on(inc, (state) => state + 1)
.on(dec, (state) => state - 1)
.on(reset, () => 0);
3) Create a Counter.jsx
component
import React from "react";
import { $counter, inc, dec, reset } from "./counterStore";
import { useStore } from "./useStore";
export const Counter = () => {
const total = useStore($counter);
return (
<>
<p>Total: <b>{total}</b></p>
<button onClick={dec}>-</button>
<button onClick={reset}>Reset</button>
<button onClick={inc}>+</button>
</>
);
};
Magic works ✨✨✨
Conclusion
We created own custom state manager and its size with useStore hook is only 1.4kb. I hope now it's a bit cleaner about how it works and how to create own state manager. Of course it needs upgrades and error handlers, but depends on your feedbacks I'll publish an article about these upgrades.
Try it on codesandbox! 🔥
Thank You For Reading!
by Orkhan Jafarov
Top comments (7)
Didnt read. <3 purely for sick header image. A+
Cool post! 👍 Do you plan to make an article about making library methods like combine?
Thanks mate! I have plan about upgrading it. I'll think about the combine method 👍 and other similar methods. (createEffect, createApi and etc)
Nice post 👍.
Isn't useReducer hook simpler to use to achieve the same goal ?
Thanks! redux-like managers have headache "a lot of" boilerplates before implement something. This kinda code (effector for example) has less code and cleaner logic
Thank you!!
Looks like good ) Thanks