A very basic state management library in under 100 lines of JavaScript
By: Vijit Ail
Tags: JavaScript, Javascript Development, Software Development

You probably must have used or at least heard about Redux, Mobx or Vuex. These libraries are used to manage state in apps built using frameworks or libraries like React, VueJS, etc. Redux is a framework-agnostic library, that means it can be used in any Single Page Application. Well, these libraries are great for most of the use cases, but when working with a simple application, you probably wouldn’t want to add another library and increase the bundle size.

In this post, I’ll walk you through, how to create a basic state management solution for JavaScript apps using the Pub/Sub pattern.

You can see the library in action here, and here’s the link to the source code.

Setup

The best way to avoid exposing your library’s internal variables in the global scope is to wrap everything in an IIFE (Immediately-Invoked Function Expression). This method is called the Module Revealing pattern.

I’m going to name this library as Kel.

Inside the IIFE, define the variables for storing the store data and events. This library is going to make use of the Pub/Sub pattern like most of the other libraries, so all the data will be passed around using events. After that, create and return a constructor function that will initialize the store with an initial value.

Next, declare two prototype functions to emit the event and subscribe for the event.

The on() method

Now let’s write the function body of the on() method. It will accept the event name, the callback and a dependency array. Think of the dependency array as mapStateToProps , but in this case, we only want to pass the store properties that are required by the callback, instead of passing the entire store.

The first step will be to check if the callback is a type of function; if it does not then log an error in the console.

Also, you need to check if dep is a type of array. typeof operator returns object for an array, so you need to rely on an alternative way.

Then, check if the event already exists in the events object; if it doesn't, then initialize it as an empty array.

And finally, you can push the dep array and the callback to the event.

The emit() method

In the emit() method, you will merge the payload with the store and send the updated store to the event's callback function. Think of emit as the equivalent of the Redux's dispatch method, but here you are specifying the event type and payload directly, instead of an object.

Before doing that, you need to do a few validations. First, check if the payload is a function. This can be the case when you want to emit the data that depends on the current store. Second, check if the payload is an object, else log an error and bail. And third, check if the event exists.

Making The Store Immutable

Well, you might want to consider passing an immutable store to the callback. To make the store immutable, you can use the Object.freeze() method, but it won't freeze the objects inside of the store. So for that, you need a recursive solution as shown below.

Now, you can pass an immutable store by using the deepFreeze() function.

Loop through the events and only pass the keys that were passed as a dependency to the callback.

Let’s make Kel a singleton

This section is completely optional, but I feel that the store should be a single source of truth for all the global data, and there shouldn’t be more than one instance of Kel .

So to do that, inside the IIFE, define a function once that will only execute the constructor function once and return the previous result if it is called a second time. You need to store the result of the function execution in a tracker variable and assign null to the function once it's executed.

And in the end, you need to return Kel wrapped in the once() function.

Counter Example

Check out a simple counter example below.

Conclusion

This was one of the simplest ways to manage state without writing complex functions or using any additional library. You could take this basic version as a starter and implement more modern JavaScript features like Proxies and do deep merge for updating the state.