2.0.1-alpha.0 • Published 4 years ago

final-state v2.0.1-alpha.0

Weekly downloads
3
License
MIT
Repository
github
Last release
4 years ago

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
2.0.1-alpha.0

4 years ago

2.0.0-alpha.0

4 years ago

1.1.3

5 years ago

1.1.2

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.0

5 years ago

0.4.1

5 years ago

0.4.0

5 years ago

0.3.0

5 years ago

0.2.9

5 years ago

0.2.8

5 years ago

0.2.7

5 years ago

0.2.6

5 years ago

0.2.5

5 years ago

0.2.4

5 years ago

0.2.3

5 years ago

0.2.2

5 years ago

0.2.1

5 years ago

0.2.0

5 years ago

0.1.4

5 years ago

0.1.3

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago