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

redux-storm

v1.0.10

Published

![logo](./redux-storm.png?raw=true)

Downloads

5

Readme

logo

Strongly Talented ORM

Prerequisites

React-redux: this is a plugin for React-Redux, therefore you must have it installed before you can start.

Installation

npm install redux-storm --save

Usage

Storm supports two types of data: collections and sets. The first is used to handle array-like data, the latter handles object-like data.

Collections

Let's say you need to store a list of users coming from an API. You need to create a model for that;

class Users {
    static $type = 'collection';
    static $table = 'users';
    static schema() {
        return {
            id: null,
            name: ''
        }
    }
}

The syntax above is allowed only if you have the @babel/plugin-proposal-class-properties plugin installed. If you don't want to use the babel plugin you can turn that declaration into something like the snippet you'll find below. However, this documentation will tend to use the syntax from the first example

const Users = {
    $type: 'collection',
    $table: 'users',
    schema() {
        return {
            id: null,
            name: ''
        }
    }
}

What have we done with that? We defined a table name ($table) for this model (that will be used to create the reducer in redux) and a type ($type), telling to storm to treat this model as a collection of users; we then defined a schema function that returns the shape of the user, within the default values (can be null).

Then we need to tell redux to use our model, so we add this to the reducers registration:

import { combineReducers } from 'redux';
import Users from '../models/Users';
// use this function to register your models
import { register } from 'redux-storm';

const reducers = combineReducers({
   // other reducers you might be using
   // ......
   //
   
   // also, this method accepts multiple models, separated by a comma, like an array
  ...register(Users),
});

export default reducers;

That's it! We are ready to go and implement the logic!

Let's have a look to a possible implementation

import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';

function UserList() {
    const Users = useModel(UserModel);
    const listOfUsers = Users.all();

    useEffect(() => {
        fetch('/api/users').then(r => r.json()).then(data => {
            Users.insert(data);
        });
    }, []);
    
    return listOfUsers.map(user => <div key={user.id}>{ user.name });
}

We want to make sure though that duplicates are not added, therefore we need a primary key

class Users {
    static $type = 'collection';
    static $table = 'users';
    static $primary = 'id';
    
    static schema() {
        return {
            id: null,
            name: ''
        }
    }
}

Now if we add another user within the same ID (this however shouldn't be a thing!), it won't be added because.

When you are using a collection, you can also define a state ($state) for you model

class Users {
    static $type = 'collection';
    static $table = 'users';
    
    static $state() {
        return {
            fetching: true
        }
    }
    static schema() {
        return {
            id: null,
            name: ''
        }
    }
}

We can now use the state inside our component, to show, for example, a loader while fetching the data

import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';

function UserList() {
    const Users = useModel(UserModel);
    const listOfUsers = Users.all();

    useEffect(() => {
        fetch('/api/users').then(r => r.json()).then(data => {
            Users.insert(data);
        });
    }, []);
    
    if (User.state().fetching) { return ( <div>Loading</div> )};
    return listOfUsers.map(user => <div key={user.id}>{ user.name });
}

But how do we tell now the fetching is completed? We'll use the .commit method:

import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';

function UserList() {
    const Users = useModel(UserModel);
    const listOfUsers = Users.all();

    useEffect(() => {
        fetch('/api/users').then(r => r.json()).then(data => {
            Users.insert(data);
            Users.commit(state => {
                state.fetching = false;
                return state;
            })
        });
    }, []);
    
    if (User.state().fetching) { 
        return ( <div>Loading</div> )
    };
    return listOfUsers.map(user => <div key={user.id}>{ user.name });
}

The component will now react accordingly and display the list when the users table is populated.

useModel for collections

When used on a collection, the useModel exposes the following methods:

all(): Array

Returns all the records for the specified model

find(predicateOrPrimaryKey: Function | Any) : Array| Object

Returns an array of records given the specified condition. Example:

const Users = useModel(UserModel);
const listOfUsers = Users.find(1); // returns the user matching the primary key
const listOfUsers2 = Users.find((user) => user.name === 'me'); // returns all the user with the name 'me'
exists(): Boolean

Returns whether the table exists. Likely to be false before any data gets added to the store.

any(): Boolean

Returns whether the table contains any data.

state(): Object

Returns the state defined in the model.

commit(changes: Function)

Allows to mutate the state of the model.

insert(data: Array | Object)

Adds a record or multiple records preventing any duplication (using the primary key or the ID to distinct)

update(data: Object, predicateOrPrimaryKey: Function | Any)

Updated a record or multiple records according to the predicate Example:

const Users = useModel(UserModel);

// updates the name to 'another me' to the user with ID 1
Users.update({
    name: 'another me'
}, 1);

// updates the name to 'another me' to every user whose age is greater that 10
Users.update({
    name: 'another me'
}, (user) => user.age > 10);
delete(predicateOrPrimaryKey: Function | Any)

Deleted the record or multiple records according to the predicate

const Users = useModel(UserModel);

// deletes the user with ID 1
Users.delete(1);

// deletes to every user whose name is nome me
Users.delete((user) => user.name !== 'me');
insertOrUpdate(data: Array | Object, predicateOrPrimaryKey: Function | Any)

Adds a new record or mulitple records if the ones matching the predicate or the primary key are not found

Example:

const Users = useModel(UserModel);

// adds the user to the store
Users.insert({
    id: 1
    name: 'me'
});

// it only adds 'another me' but updates 'me' with 'me again' because they have the same ID
Users.insertOrUpdate([
    {
        id: 2,
        name: 'another me'
    },
    {
        id: 1,
        name: 'me again'
    }
])

Sets

Let's say we need to store whether the user is authenticated or not in our application. For this purpose we are going to create a different type of model:

class Authentication {
    static $type = 'set';
    static $table = 'auth';

    static schema() {
        return {
            isLoggedIn: false,
            username: null
        }
    }
}

then we add it to store like we did for the Users model:

import { combineReducers } from 'redux';
import Users from '../models/Users';
import Authentication from '../models/Authentication';
// use this function to register your models
import { register } from 'redux-storm';

const reducers = combineReducers({
   // other reducers you might be using
   // ......
   //
   
  ...register(
      Users, 
      Authentication
  ),
});

export default reducers;

Let's return to our component:

import React, { useEffect } from 'react';
import { useModel } from 'redux-storm';
import UserModel from '../models/Users';
import AuthenticationModel from '../models/Authentication';


function UserList() {
    const Users = useModel(UserModel);
    const Auth = useModel(AuthenticationModel);
    const loggedUser = Auth.get();

    const listOfUsers = Users.all();

    useEffect(() => {
        Auth.set('username', 'me1245');
        Auth.set('isLoggedIn', true);

        fetch('/api/users').then(r => r.json()).then(data => {
            Users.insert(data);
        });
    }, []);
    
    if (User.state().fetching) { return ( <div>Loading</div> )};

    return (
        <div>
            {
                loggedUser && <h1>Welcome back { loggedUser.username }</h1>
            }
            <div className="list">
                { listOfUsers.map(user => <div key={user.id}>{ user.name }); }
            </div>
        </div>
    )
}

You might have noticed the set type does not provide a state: this is because a set is a state itself, so it would be redundant.

useModel for set

When used on a set, the useModel exposes the following methods:

set(key: String, data: Object | Any)

Sets an object against the model

unset(key: String)

Removes the specified key from the set

Example

Auth.set('name', 'me');
Auth.set('age', 30);
Auth.set('birthday', {
    day: 30,
    month: 12
})

// this will remove the age
Auth.unset('age');

// this will only remove the month 
Auth.unset('birthday.month')
get(key: String | null)

Returns the entire object if no params are passed or the specified key

Example

Auth.set('name', 'me');
Auth.set('age', 30);
Auth.set('birthday', {
    day: 30,
    month: 12
});

Auth.get() // return the entire object
Auth.get('age') // only returns the age
Auth.get('birthday.day') // only returns 30

Non hooks version

Although it is recommended to use the hooks based version, you can still use the old redux API with connect.

import React, { useEffect } from 'react';
import { withModels } from 'redux-storm';
import UserModel from '../models/Users';
import AuthenticationModel from '../models/Authentication';


function UserList({ Users, Authentication }) {
    const loggedUser = Auth.get();

    const listOfUsers = Users.all();

    useEffect(() => {
        Auth
            .set('username', 'me1245')
            .set('isLoggedIn', true)

        fetch('/api/users').then(r => r.json()).then(data => {
            Users.insert(data);
        });
    }, []);
    
    if (User.state().fetching) { return ( <div>Loading</div> )};

    return (
        <div>
            {
                loggedUser && <h1>Welcome back { loggedUser.username }</h1>
            }
            <div className="list">
                { listOfUsers.map(user => <div key={user.id}>{ user.name }); }
            </div>
        </div>
    )
}


export default connect()(withModels(UserList, UsersModel, AuthenticationModel));
withModels(Component, ...models)

Accepts the component and a list of models to use that will be passed as props. Always use this inside the connect and not the other way

Yes:

export default connect()(withModels(UserList, UsersModel, AuthenticationModel));

No:

export default withModels(connect()(UserList), UsersModel, AuthenticationModel));