Create Your Effector-like State Manager

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;
};
Enter fullscreen mode

Exit fullscreen mode

  1. 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;
};
Enter fullscreen mode

Exit fullscreen mode

  1. 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;
};
Enter fullscreen mode

Exit fullscreen mode

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;
};
Enter fullscreen mode

Exit fullscreen mode

  1. 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);
Enter fullscreen mode

Exit fullscreen mode

  1. 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>
    </>
  );
};
Enter fullscreen mode

Exit fullscreen mode

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

Author: admin

Leave a Reply

Your email address will not be published.