npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

final-state

v2.0.1-alpha.0

Published

[![Build Status](https://travis-ci.com/final-state/final-state-monorepo.svg?branch=master)](https://travis-ci.com/final-state/final-state-monorepo) [![codecov.io](https://codecov.io/gh/final-state/final-state-monorepo/branch/master/graph/badge.svg)](https

Downloads

38

Readme

Build Status codecov.io Known Vulnerabilities styled with prettier lerna minified + gzip

final-state

A lightweight, framework agnostic state management.

Installation

# Install peer dependencies
yarn add immer
# Install final-state
yarn add final-state

final-state is written in Typescript, so you don't need to find a type definition for it.

Basic Example

import { createStore } from 'final-state';

// Define initial state
const initialState = {
  a: 1,
  b: 'good',
};

// Define actions
const actions = {
  increaseA(draftState, n = 1) {
    draftState.a += n;
  },
};

// Create store instance
const store = createStore(initialState, actions, 'store-name');

// Print state
console.log('INITIAL STATE:', store.getState());

// Define a listener to listen the changes of state
function listener() {
  // Print state
  console.log('IN SUBSCRIBE LISTENER:', store.getState());
}

// Subscribe the changes of state
store.subscribe(listener);

// Dispatch action
store.dispatch('increaseA');

// Print state
console.log('CURRENT STATE:', store.getState());

store.unSubscribe(listener);

/* Output will be:
INITIAL STATE: Object {a: 1, b: "good"}
IN SUBSCRIBE LISTENER: Object {a: 2, b: "good"}
CURRENT STATE: Object {a: 2, b: "good"}
*/

API Reference

createStore(initialState, actions[, name])

Create a store instance. You can create multiple stores in your app.

Parameters:

  • initialState is the initial state, it can be any type.
  • actions is all the actions that work for this store. It looks like this:
    const actions = {
      someFooAction(draftState, params) {
        // do something
      },
      someBarAction: draftState => {
        // do something
      },
      async someAsyncAction(draftState) {
        // do some async works
      },
    };
    The signature of action function is:
    (draftState[, actionParams]) => {
      // To mutate draftState directly!
      // No need to return anything!
      // Changes will be merged into real state object immutably.
    }
  • name is optional. It will give this store instance a name. If you don't give it a name, it's default name is `NONAME_STORE${Date.now()}`.

Store#getState()

Get the latest state object. Keep in mind that you shouldn't mutate the state object directly. It will not take effect until next Store#dispatch and may cause your app broken.

Store#subscribe(listener)

Subscribe the changes of state. Once the state are changed by Store#dispatch, the listener will be called.

It returns a function to let you unsubscribe this listener:

const unSubscribe = store.subscribe(listener);
unSubscribe();

listener

The listener is a function with the following signature:

/**
 * Listener type
 *
 * @template T the type of your state
 */
export type Listener<T = any> = (type?: string, prevState?: T) => void;
  • type lets you know which action causes this change.
  • prevState lets you know the previous state.
  • You can call Store#getState in listener to get the latest state.

A basic example of using type and prevState:

// final-state-logger
store.subscribe((type, prevState) =>
  console.log(type, prevState, store.getState()),
);

Store#unSubscribe(listener)

Unsubscribe a listener. The listener should exactly be same with the one passed to Store#subscribe.

Store#dispatch(action[, params]) [overload]

// definition
/**
 * An overload of dispatch
 *
 * Dispatch an action that has been defined and named.
 * @param {string} action the name of action
 * @param {K} params the type of your action parameters
 * @template K the type of your action parameters
 * @returns a promise to indicate the action is totally finished
 */
public dispatch<K = undefined>(action: string, params?: K): Promise<void>;

Dispatch an action to alter state.

The first parameter action is the name of the action function which will be triggered.

The second parameter params is the dynamic values that are used by action function.

It returns a Promise to indicate whether the action is totally finished.

store.dispatch(...).then(() => {
  // action is totally finished
});

⚡️️️️️️️Important Notes!!!

When you dispatch an async action like this:

const actions = {
  async someAsyncAction(draftState) {...},
};
// ...
store.dispatch('someAsyncAction');
// store.getState() is still old state

store.dispatch('someAsyncAction').then(() => {
  // store.getState() is the latest state
});

You can't get the latest state right after dispatching. Because as it's name says, it is asynchronous.

Store#dispatch(action[, params]) [overload]

// definition
/**
 * An overload of dispatch
 *
 * Dispatch an action that is defined temporarily
 * @param {Action<T, K>} action the action function
 * @param {K} params the type of your action parameters
 * @template K the type of your action parameters
 * @returns a promise to indicate the action is totally finished
 */
public dispatch<K = undefined>(
  action: Action<T, K>,
  params?: K,
): Promise<void>;

Dispatch an action to alter state.

The first parameter action is the action function which will be triggered.

The second parameter params is the dynamic values that are used by action function.

It returns a Promise to indicate whether the action is totally finished.

Async action function is also supported.

Store#registerActionHandler(name, handler)

Anyone can write his own action handler to handle the custom actions.

The first parameter name is the name of your handler.

The second parameter handler is a function with the following signature:

/**
 * Type of plugin handler to handle actions
 * @param {PluginAction} pluginAction the action object of plugins
 * @param {any} params the parameters of action
 */
export type ActionHandler = (pluginAction: PluginAction, params?: any) => void;

Let's see a simple example:

// Register a custom handler that can handle observable actions
import { Observable } from 'rxjs';
import { createStore } from 'final-state';

const initialState = {
  a: 0,
};

const actions = {
  increaseA(draftState, n = 1) {
    draftState.a += n;
  },
  rxIncreaseA: {
    handler: 'rx',
    action(n = 1) {
      return new Observable(subscriber => {
        subscriber.next(['increaseA', n]);
        setTimeout(() => {
          subscriber.next('increaseA');
          subscriber.complete();
        }, 200);
      });
    },
  },
};

const store = createStore(
  initialState,
  actions,
  'custom-handler-example-store',
);

store.registerActionHandler('rx', (pluginAction, params) => {
  return new Promise((resolve, reject) => {
    pluginAction.action(params).subscribe({
      next(value) {
        if (Array.isArray(value)) {
          store.dispatch(...value);
        } else if (typeof value === 'string') {
          store.dispatch(value);
        }
      },
      error: reject,
      complete() {
        resolve();
      },
    });
  });
});

store.dispatch('rxIncreaseA', 5);

// a = 5 now
// after 1000 milliseconds, a = 6

Use with typescript

import { createStore, Action, ActionMap } from 'final-state';

// Define state shape
interface State {
  a: number;
  b: string;
}

// Define initial state
const initialState: State = {
  a: 1,
  b: 'good',
};

// Define actions
const actions: ActionMap<State> = {
  increaseA(draftState, n = 1) {
    draftState.a += n;
  },
};

// Create store instance
const store = createStore<State>(initialState, actions);

// Print state
console.log('INITIAL STATE:', store.getState());

// Define a listener to listen the changes of state
function listener() {
  // Print state
  console.log('IN SUBSCRIBE LISTENER:', store.getState());
}

// Subscribe the changes of state
store.subscribe(listener);

// Dispatch action
store.dispatch('increaseA');

// Print state
console.log('CURRENT STATE:', store.getState());

store.unSubscribe(listener);

/* Output will be:
INITIAL STATE: Object {a: 1, b: "good"}
IN SUBSCRIBE LISTENER: Object {a: 2, b: "good"}
CURRENT STATE: Object {a: 2, b: "good"}
*/

Test

This project uses jest to perform testing.

yarn test