simple-data-store
    TypeScript icon, indicating that this package has built-in type declarations

    3.3.1 • Public • Published

    Simple Data Store

    NPMBadge for Gzip size

    A Typescript based simple data store for state management.

    My alternative to redux and other state management.

    Install

    To get from npm simply run.

    npm install --save simple-data-store

    Alternatively you can download the code and build it yourself with

    npm run build

    And in the dist folder will be the Javascript and Typescript typings file.

    Also the whole thing is one Typescript file so it's pretty easy to manually add it to your own source code.

    Features

    • Small file size (about 0.5kb after compression)
    • Immutable.
    • Simple API.
    • Has support for history, with an example implementation found here
    • Selector support (along the lines of reselect for redux).
    • No dependencies

    Why?

    Do we ever need more JS/TS libraries?

    I like what redux and other functional state management libraries have done for UI but I found them too unstructured and too generalised. So I put together my own version that is still general but puts just enough structure to fulfil my needs.

    Example

    Modifiers are functions that perform an action on the state. This means that the state does not need to know about modifiers at all.

    In the example below it is shown that creating a function that returns the modifier with the values in the closure is useful.

    import DataStore from "../src";
    
    interface SimpleState
    {
        readonly counter: number;
    }
    
    function change(value: number)
    {
        return (state: SimpleState) => ({ counter: state.counter + value });
    }
    
    const store = new DataStore<SimpleState>
    ({
        counter: 0
    });
    
    store.subscribeAny((state) => console.log(state));
    
    store.execute(change(1));
    store.execute(change(2));
    store.execute(change(-1));
    
    /* Example output:
    { counter: 1 }
    { counter: 3 }
    { counter: 2 }
    */

    There is a longer example at example/sample.ts.

    API

    Types

    // A function to create a patch for the state.
    // Meaning this should return a partial state to be merged with the current state.
    export type Modifier<TState> = (state: TState) => Partial<TState> | null;
    
    // A function that takes part of the state and returns a sub set of that state.
    // Used to look for specific parts of the state that have changed.
    export type Selector<TState, TValue> = (state: TState) => TValue;
    
    // A function used to compare if two parts of the state have actually changed.
    // By default a strict equals is used when comparing however sometimes something more complex is needed.
    // The TValue refers to the value returned by the Selector.
    export type SelectorComparer<TValue> = (prevValue: TValue, newValue: TValue) => boolean;
    
    // A callback function to be triggered when a selector has returned a new value.
    // The callback is given the new state and result of the selector that triggered the callback.
    // The TValue refers to the value returned by the Selector.
    export type Subscription<TState, TValue> = (state: TState, newValue: TValue, triggeringModifier: Modifier<TState>, isNewState: boolean) => void;
    
    // A function used to remove a subscription. This can be called multiple times.
    export type RemoveSubscription = () => void;

    DataStore

    The main data store class. Keeps track of the current state, any subscriptions and optionally a history of the state.

    By default history is disabled.

    Constructor

    initialState: TState The initial state of the store.

    The constructor requires an initial state for the store that fits the store's state interface.

    interface State
    {
        readonly name: string;
        readonly age: number;
        readonly interests: string[];
    }
    
    const store = new DataStore<State>({
        name: 'Unset',
        age: 0,
        interests: []
    });

    state

    returns: Readonly<TState> The current state.

    Returns the state marked specifically as readonly. The readonly part is not usually required but just to make it very clear.

    execute

    modifier: Modifier<TState> The modifier function to patch the state with.

    Executes a modifier on the state. The modifier is recommended to return a partial state that is merged.

    If the modifier returns the same state (as compared with strict equals) or null then the state is not updated nor is any subscription triggered.

    subscribe

    selector: Selector<TState, TValue> A function for picking the values out of the store you want to check when changed.

    subscription: Subscription<TState, TValue> A callback that will be triggered when the values returned by the selector has changed.

    comparer: SelectorComparer<TValue> An optional comparer for the old and new values in the selector. Defaults to strict equals.

    selectorName: string An optional name for the selector, can be used to help identify the selector when debugging.

    returns: RemoveSubscription A function to remove the subscription from the store.

    Subscribe a callback to be triggered when a part of the state has changed.

    import DataStore from "../src";
    
    interface State
    {
        age: number;
        name: string;
    }
    
    const store = new DataStore<State>
    ({
        age: 30,
        name: "Fluff"
    });
    
    let numberOfAgeChanges = 0;
    
    // Subscribe to any changes to the age by returning the age from the state which will be compared between execute calls.
    store.subscribe((state) => state.age, (state, newAge) => console.log('New Age', newAge));
    
    // Subscribe to any changes to the name by returning the name from the state which will be compared between execute calls.
    store.subscribe((state) => state.name, (state, newName) => console.log('New Name', newName));
    
    // Subscribe to any changes to the age by adding our own way of comparing age changing.
    store.subscribe((state) => state, (state) => numberOfAgeChanges++, (prev, next) => next.age === prev.age);
    
    store.execute((state) => ({age: 35}));
    store.execute((state) => ({name: "Puff"}));
    store.execute((state) => ({age: 40}));
    store.execute((state) => ({age: 40}));
    
    console.log('Number of age changes', numberOfAgeChanges);
    
    /* Example output
    New Age 35
    New Name Puff
    New Age 40
    Number of age changes 2
    */

    subscribeAny

    subscription: Subscription<TState, TState> A callback that will be triggered when the state has changed.

    selectorName: string An optional name for the selector, can be used to help identify the selector when debugging.

    returns: RemoveSubscription A function to remove the subscription from the store.

    A shorthand subscribe function that will trigger the callback when the state changes at all.

    unsubscribeAll

    Removes all subscriptions from the store.

    Benchmarks

    There's a benchmarks folder with more details however the summery is that SimpleDataStore is roughly similar to redux in terms of performance.

    The pros are that it doesn't really matter how many state modifiers you have because there's no additional lookup time to find them compared to reducers.

    The cons for very simple states, where the state is a primitive value or the reducer returns the whole state without needing to be combined it's slower than Redux.

    The main reason being that SimpleDataStore assumes that the state is always an object and the modifiers will always return a partial state and combines using Object.assign. Combined to Redux that always assumes a full state object.

    License

    MIT

    Author

    Alan Lawrey 2021

    Install

    npm i simple-data-store

    DownloadsWeekly Downloads

    99

    Version

    3.3.1

    License

    MIT

    Unpacked Size

    31.9 kB

    Total Files

    9

    Last publish

    Collaborators

    • alan.lawrey