Socket
Socket
Sign inDemoInstall

@persevie/statemanjs

Package Overview
Dependencies
0
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    @persevie/statemanjs

Proper state manager for JavaScript


Version published
Maintainers
1
Install size
69.4 kB
Created

Readme

Source

Statemanjs logo

import { createState } from "@persevie/statemanjs";

const counterState = createState(0);

counterState.subscribe((state) => {
    if (Number.isInteger(state)) {
        console.log("it's integer");
    } else {
        console.log("it's not integer");
    }
});

function increaseCount() {
    counterState.set(counterState.get() + 1);
}
type TransferElement = {
    speed: number;
    info: string;
    error?: string;
};

const transferState = createState<TransferElement>({
    speed: 0,
    info: "start",
});

transferState.subscribe(
    // called only if the error property has been updated
    (state) => {
        console.log("Oops, something seems to be broken");
    },
    {
        notifyCondition: (state) => {
            state.error !== undefined;
        },
    },
);

Table of Contents

Review

Statemanjs is a framework agnostic library for creating and managing the state of your JavaScript and NodeJS applications. Statemanjs is written in TypeScript so it has excellent support out of the box. statemanjs has integrations (delivered as npm packages) for front-end frameworks (react, vue, solid, etc.) that just make it easier to use. You can do without them if you wish. Statemanjs has the highest performance and reliability, and adheres to a clear and strict API without a boilerplate. Statemanjs is highly scalable and suitable for both small and large projects.

Here are the basic principles of statemanjs:

  • Performance
  • Reliability
  • Clear API
  • Everything can be a state

Each of these principles will be discussed below.

Performance

Statemanjs was developed for JavaScript with all the features of this language in mind. Special attention is paid to single-threading and mutability. Most state managers for JS take an immutable approach. This means that when the state changes, a copy of it is always created. It can be imagined like this - a new value of the object has come → the state is cloned → the state is updated → the updated state is returned. Now it does not look so scary, but let's add single-threading to this. Your application is forced to wait for the state update to complete before doing something else. It would be more logical to follow this approach - a new value of the object has come → the state is updated → the updated state is returned. The number of stages has been reduced, and therefore productivity has increased. “But you only threw out cloning, does it affect performance so much?” - Yes. In JavaScript, immutability is a very expensive operation. This means that the cloning step will take time, which can be spent, for example, updating the UI or performing another task. Add to this a huge amount of logic in your application, and the performance difference becomes more and more significant. Statemanjs - takes a data mutability approach.

Reliability

The immutable approach ensures that your state is not accidentally changed, which is not the case with the mutable approach. For example, the state of Mobx can be changed anywhere and any way. You can bind the current state to a variable, and when the variable changes, the state will also change. Agree, it does not look very reliable. Statemanjs is arranged differently here as well. You can only change/create state through built-in methods. It is this API that guarantees the reliability of your state.

Clear API

As it was written above, any manipulations with your state are possible only through built-in methods, so they should be understandable and convenient. The createState method is used to create a state:

createState<T>(element: T): StatemanjsAPI<T>;

There are 6 methods for interacting with the state - set, get, subscribe, unsubscribeAll, getActiveSubscribersCount, update.

Here is a detailed view of the API:

/**
 * Accepts a new state and compares it with the current one.
 * Nothing will happen if the passed value is equal to the current one.
 * @param newState New state.
 * @returns Status of operation.
 */
set(newState: T): boolean;

/** Get current state */
get(): T;

/**
 * The method of subscribing to the state change.
 * Accepts a callback function (subscription callback),
 * which will be called at each update, and a subscription options object.
 * In the options, you can specify information about the subscription,
 * as well as specify the condition under which the subscriber will be notified.
 * Returns the unsubscribe callback function.
 *
 * @param subscriptionCb A function that runs on every update.
 * @param subscriptionOptions Additional information and notification condition.
 * @returns Unsubscribe callback function.
 */
subscribe(subscriptionCb: SubscriptionCb<T>, subscriptionOptions?: SubscriptionOptions<T>): UnsubscribeCb;

/** Remove all subscribers */
unsubscribeAll(): void;

/**
 * Returns counter of all active subscribers.
 * @returns number.
 */
getActiveSubscribersCount(): number;

/**
 * Flexible state update.
 * @param updateCb Callback for state updates.
 */
update(updateCb: UpdateCb<T>): void;

Everything can be a state

A state can be anything from primitives to complex and multidimensional objects. Just pass this to the createState function and use the state with no extra effort.

const isLoading = createState(true);

const soComplexObject = createState({
    1: { 2: { 3: { 4: { 5: [{ foo: "bar" }] } } } },
});

Usage

Installing and creating state

npm i @persevie/statemanjs
import { createState } from "@persevie/statemanjs";

const counterState = createState(0);

You can also pass in the type of your state if you are using TypeScript:

import { createState } from "@persevie/statemanjs";

type User = {
    name: string;
    age: number;
};

const userState = createState<User>({ name: "Finn", age: 13 });

To get the current state, use the get method.

const counterState = createState(1);

const counter = counterState.get(); // 1

Subscribe to changes

The subscribe method takes a callback function and executes it on every state change. This callback function accepts the updated state.

const counterState = createState(0);

// the 'state' parameter is the updated (current) state
counterState.subscribe((state) => {
    if (Number.isInteger(state)) {
        console.log("it's integer");
    } else {
        console.log("it's not integer");
    }
});

You can set a condition, notifyCondition, under which the callback will be called. This condition is the second and optional parameter. If there is no condition, then the callback will fire on every state change. notifyCondition also accepts the updated state.

const counterState = createState(0);

counterState.subscribe(
    (state) => {
        console.log("it's integer");
    },
    { notifyCondition: (state) => Number.isInteger(state) },
);

The subscribe method returns a callback to unsubscribe.

const counterState = createState(0);

const unsub = counterState.subscribe(
    (state) => {
        console.log("it's integer");
    },
    { notifyCondition: (state) => Number.isInteger(state) },
);

// cancel subscribe
unsub();

To unsubscribe all active subscriptions from a state, use the unsubscribeAll method;

counterState.unsubscribeAll();

Sometimes you need to find out how many active subscriptions a state has, for this there is a getActiveSubscribersCount method.

const subscribersCount = counterState.getActiveSubscribersCount();

State change

There are two ways to change the state - set and update. The set method completely changes the state and is great for primitives and simple states.

const counterState = createState(0);

counterState.subscribe(
    (state) => {
        console.log("it's integer");
    },
    { notifyCondition: (state) => Number.isInteger(state) },
);

counterState.set(2); // --> 2

counterState.set(counterState.get() * 2); // --> 4

The update method is suitable for complex states (objects and arrays) in which only part of the state needs to be changed. The update method accepts the current state.

import { createState } from "@persevie/statemanjs";

type User = {
    name: string;
    age: number;
    isOnline: boolean;
    hobbyes: Array<string>;
};

const userState = createState<User>({
    name: "Finn",
    age: 13,
    isOnline: false,
    hobbyes: [],
});

userState.update((state) => {
    state.isOnline = !state.isOnline;
});

userState.update((state) => {
    state.hobbyes.push("adventure");
});

Benchmark

The benchmark was run on a MacBook Pro, m1, 16gb. You can run it on your device.

Below is a comparison table with other popular state managers. This table is the result of the benchmark, which adds elements to the array (state), in the amount of 100, 500, 1000, 5000, 10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, 2560000. Each case was run 10 times (for all state managers) and the average value was recorded in a table.

*❌ - means error during benchmark.

**Results in seconds

Name100500100050001000020000400008000016000032000064000012800002560000
statemanjs0.12567500025033950.416037499532103560.64479180052876471.54764180034399022.67947910018265265.2179707005620019.45973329991102216.60190850012004333.2470248999539859.56564570013434125.51550420001149270.7810873998329532.6712166998535
redux0.129833100363612171.35613320022821434.40060419999063266.93285399936141009.341375098754177.27396260015719662.740120899678105281.26535429992
mobx0.72773329988121982.7062582999467854.10844580009579613.1813374999910624.97171659953892247.7444376002997299.50159570015967200.66793330013752452.04461259990931100.9856083998457
xstate1.51143319979310035.0767000008374459.00342909991741226.32199170030653347.31824579983949592.908674999699184.9398877006024353.5915873996913739.15685849995351552.73399569988263419.5948792006828327.19787920005522651.708679099753
effector0.456754099577665331.22377490028738971.65018329955637453.25164589993655674.7593459006398918.27659999988973113.91046249978244423.2324418000876946.6320499999914381.9146542005241174.61498729977757378.94245009999725745.4281083000824

As you can see from the table, statemanjs shows the best results. Pay close attention to the performance difference between statemanjs and mobx. Both state managers rely on state mutability, but the difference in the implementation of this approach sets them apart. In other words, variability is their only similarity. When looking at this table, keep in mind that the performance of most state managers will be slower on real projects. For example, Redux currently has only one body, in reality there will be more reducers. On the other hand, Statemanjs scales well in both depth (state size) and width (number of states). Don't forget that the benchmark was run in the NodeJS environment, everything will be slower in the browser. Also, user devices can be different, which also affects performance.

Integrations

Statemanjs is framework agnostic and can be used without additional packages. But for convenience, there are packages for the most popular frameworks - react, vue, solid. Statemanjs supports svelte out of the box and doesn't need any additional packages. To work with additional packages, the main statemanjs package is required.

For contributors

See CONTRIBUTING.md.

Keywords

FAQs

Last updated on 16 Nov 2022

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc