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-simple-models

v0.6.0

Published

getters and setters for redux models

Downloads

4

Readme

redux-simple-models

Coverage Status Build Status

Introduction

redux-simple-models is a simple interface for creating, updating, deleting (aka CRUD) and retrieving data from a redux store. It takes care of creating a default set of actions, a standard reducer, and a set of selectors for retrieving data.

We set up our store with a reducer for our example model myModel, like so:

> import { createStore, combineReducers } from 'redux';
> import { actions, reducer, selectors } from 'redux-simple-models';
> const store = createStore(combineReducers({
    entities: combineReducers({ myModel: reducer('myModel') })
  }))
  

This adds our standard reducer to the store, tailored for the myModel model.

To store data in redux store:

> store.dispatch(actions.create('myModel')({ some: 'model data' }));
> store.dispatch(actions.create('myModel')({ example: 'usage', asdf: true }));

Get data from store (note automatic integer ids, this is optional):

> selectors.get('myModel')(store.getState());
[{ id: 1, 'some': 'model data'}, {id: 2, 'example': 'usage', 'asdf': true }]

Get data from store satisfying some where parameters, in this case, that :

> selectors.get('myModel')(store.getState(), {'example': 'usage'})
[{ id: 1, 'example': 'usage', 'asdf': true }]

Update data:

> store.dispatch(actions.update('myModel')({ id: 1 }, {'example': 'new'}))
> selectors.getOne('myModel')(store.getState(), {'example': 'new'})
{ id: 1, 'example': 'new', 'asdf': true }

Delete model by id

> store.dispatch(delete('myModel')({ id:1 }))
// now there is only a single remaining model in the store
> selectors.get('myModel')(store.getState())
[{ id: 2, 'example': 'usage', 'asdf': true }]

Installation

$ npm install redux-simple-models --save

This is a UMD build file, and should work across various build systems and javascript environments.

Usage

Reducer and store setup

First setup the store with the required model reducers. Each model needs its own reducer, and we put all of our models within a store namespace (default is entities).

import { actions, reducer, selectors } from 'redux-models';
import { createStore } from 'redux';

const entityReducers = combineReducers({
    myModel: reducer('myModel'), // a single model called 'myModel'
});

const topLevelReducer = combineReducers({
    entities: entityReducers, 
});

const store = createStore(topLevelReducer);

Creating model instances

We can add instances to the store using actions.create and telling it the name of the model each time:

const modelInstance = { hello: 'world', example: true };
const storeAction = actions.create('myModel')(modelInstance);
store.dispatch(storeAction);

But, for convenience, we create a customised create function for our model:

const myModelCreate = actions.create('myModel', true);

Then we simply pass model data directly to this closure:

store.dispatch(myModelCreate({some: 'test', data: 'here'}));
store.dispatch(myModelCreate({some: 'more test', data: 'here'}));

The above create calls result in the store data structure looking like:

>> store.getState()
{
   "entities": {
       "myModel": {
           1: { hello: 'world', example: true },
           2: {some: 'test', data: 'here'},
           3: {some: 'more test', data: 'here'},
       }
   }
}

If you don't want automatic integer ids, simply pass an id property in the model data:

>> store.dispatch(myModelCreate({ id: 'hello', some: 'test data' }))
>> store.getState()
{
   "entities": {
       "myModel": {
           1: { hello: 'world', example: true },
           2: {some: 'test', data: 'here'},
           3: {some: 'more test', data: 'here'},
           hello: {some: 'test data'},
       }
   }
}

Bulk creating instances

We can bulk create instances using the bulkUpdate() action. This will directly set the contents of the models store, overwriting existing models with the same id.

let bulkCreateMyModel = actions.bulkCreate('myModel');
store.dispatch(bulkCreateMyModel(
    {
        2: {new: 'model data', overrides: 'old data', for: 'model instance id 2' },
        4: {'this': 'is a new model instance'},
    }
));

If there are multiple models that we want to bulk create for (i.e. when rehydrating the application state), we can use allModelBulkCreate() achieve this.

const newData = actions.allModelBulkCreate({
  myModel: {
    rehydrated: { model: 'data' },
  },
  anotherModel: {
    more: { data: 'here' }
  },
  etc: {}
});
store.dispatch(newData);

You still need reducers in the store for each model, any model in the allModelBulkCreate call that does not have a corresponding reducer in the store will be ignored. This call will also overwrite any existing model data for any model that has data in the call.

This function is the only one that does take a model name as the first call, as it updates all models and does not require specialisation for one.

Updating instances

Updating instances has a similar interface. It takes an object where clause as the first argument, and the instance properties you want to add or overwrite as the second argument. If the where clause matches multiple models, all of these models will be updated.

let updateMyModel = actions.update('myModel');
store.dispatch(updateMyModel({ id: 1 }, { 
    newProperty: 'asdf',
    hello: 'new data',
}));

If you have some fancy models that need to do more than just update a property, you can provide a customReducer function as the last argument. This customReducer function takes the single entity, and returns the updated entity.

store.dispatch(updateMyModel({ id: 1 }, undefined, (entity) => ({
    a: 3,
})));

Retrieving instances

Retrieving data from the store is done via the selectors interface. The selectors interface takes the store state, and a where object, specifying the attributes that the model objects need to contain in order to be returned. To return all instances, just omit a where object from the second argument.

// create our customised selector
const myModelGet = selectors.get('myModel');

// get everything
myModelGet(state)

// only get items that satisfy some 'where' conditions
myModelGet(state, { data: 'here' })

If we only want a single model instance the getOne interface is provided.

const myModelGetOne = selectors.getOne('myModel');
myModelGetOne(state, { id:1 })

If no instances is found, or more than one instance is found, it will throw an Exception detailing the error.

Array where parameters special behaviour

If you specify an array in any where clauses, such as:

myModelGet(state, { key: [1, 2, 3] })
// or 
actions.update({ new: 'data' }, { anotherKey: [3] })

This will not exactly match the model contents as is the case with strings and numbers, it will match if it is a subset of the model array. An example with a pre-prepared store:

> store.getState()
{
   "entities": {
       "myModel": {
           1: { hello: 'world', related: [2, 3] },
           2: {some: 'test', related: [2, 3, 4]},
           3: {some: 'more test', related: [8, 9]},
       }
   }
}
> getMyModel(store.getState(), { related: [2, 3] })
[ 
  { id: 1, hello: 'world', related: [2, 3]},
  { id: 2, some: 'test', related: [2, 3, 4]}
]

Note that the query specifies [2, 3] but two models are returned, because [2, 3] is a subset of each models related value.

rsm-create

All of these functions are closures that specialise themselves based on the first function call. This is done because it is expected that a specialised version will live within its own file and be exported.

For example, your directory structure might look like:

├── models
│   ├── myModel
│   │   ├── actions.js
│   │   ├── reducers.js
│   │   ├── schema.js
│   │   └── selectors.js
│   ├── forms
│   │   ├── actions.js
│   │   ├── reducers.js
│   │   ├── schema.js
│   │   └── selectors.js
│   ├── widgets
│   │   ├── actions.js
│   │   ├── reducers.js
│   │   ├── schema.js
│   │   └── selectors.js

A script for creating model folders is shipped with this package (rsm-create), and can be used like:

$ rsm-create myModel

This script will create a folder in the terminal current working directory with customised files for the model name, i.e. if you called $ rsm-create myModel, in the actions.js file you would have:

// actions.js
import { actions } from 'redux-models';
export const create = actions.create('myModel');
export const update = actions.update('myModel');
export const del = actions.del('myModel');

Now the myModel actions can be imported and used directly wherever they are needed.

import * as myModelActions from './models/myModel/actions.js';
myModelActions.create({ test: true });