Introducing Socket Firewall: Free, Proactive Protection for Your Software Supply Chain.Learn More
Socket
Book a DemoInstallSign in
Socket

redux-easy-models

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redux-easy-models

Easily create models using standard redux architecture.

latest
npmnpm
Version
0.0.4
Version published
Maintainers
1
Created
Source

redux-easy-models

Easily define your models using standard redux libraries (redux, redux-actions, redux-thunk).

(This package is still experimental)

Installation

npm install --save redux-easy-models

Rationale

In a plain vanilla redux implementation with the help of some helper libraries like redux-actions and redux-thunk you might end up with something like this:

actions/timer.js

const start = createAction("TIMER_START")
//... more action creators here.
export default { start };

reducers/timer.js

import { start } from '../actions/timer';
const startReducer = handleAction(start, {
  next(state, action) {...},
  throw(state, action) {...}
});

Later in you containers or components you'd have to dispatch actions like this:

import { start } from '../actions/timer';
//...
dispatch(start());

While this decouples the code and keeps things simple, the amount of boilerplate and disconnection between actions and reducers can feel painful at times. With redux-easy-models you could achieve the same with the following code:

models/timer.js

export default new ReduxModel({
  name: timer,
  actions: [ 'start' ],
  reducers: {
    start: (state, action) => ...
  }
});

Notice how all the code for the "timer" model is in a single file. When you want to dispatch your actions, all you have to do is:

import model = '../model/timer';
//...
model.api.start();

This will automatically dispatch standard actions with standar action types. In this case the action would look like this:

{
  type: "TIMER_START"
}

redux-easy-models supports defining actions with sync and async functions, and chaining multiple actions.

Demo

For a working demo you can go here.

Usage

import ReduxModel from 'redux-easy-models';

Define your models using a single configuration object:

const timer = {

  // the name of the model. we will use this name to force
  // a convention where every action will have a type with
  // the name as the prefix. Can be used to combine reducers
  // as well. ie:
  //    const reducers = combineReducers({
  //      [timerModel.name]: timerModel.reducer
  //    });
  name: 'timer',
  
  // reducer's initial state.
  initialState: { started: false, count: 0, timerId: null },
  
  // actions can either be strings or named functions.
  actions: [
    // in case of strings, action creators will be generated
    // with redux-actions. the name of the model will be used
    //  as a prefix as well, ie:
    //     let actionCreator = createAction('TIMER_INCREASE');
    'increase',
    'clear',
    // functions should be named and will automatically
    // generate two actions, one when the execution starts,
    // one in case of success or failure.
    // action types will follow an upper snake convention with
    // the name as the prefix, ie:
    //   function doSomething() {...
    // will be translated to an action type of:
    //   TIMER_DO_SOMETHING_START
    //   TIMER_DO_SOMETHING_SUCCESS
    //   TIMER_DO_SOMETHING_FAIL
    function start() {
      this.clear();
      return setInterval(this.increase, 1000);
    },
    function getTimerId() {
      return this.getMyState().timerId;
    },
    function stop() {
      let timerId = this.getTimerId();
      clearInterval(timerId);
    },
    // functions can be async and/or return a promise,
    // and the right success/fail action will be generated based
    // on whether the promise is resolved/rejected.
    // in case the function is not async and is sync,
    // start/success/fail actions will also be generated, and
    // the execution will be wrapped in a try/catch statement
    async function delayStart() {
      await delay();
      this.start();
    }
  ],
  
  // reducers can be defined for actions generated by this model.
  // no need to use prefix, and sufix in the case of 'Success' is
  // options, so in the following list 'start' and 'startSuccess'
  // are pretty much the same.
  reducers: {
    start: (state, action) => {
      return Object.assign({}, state, { started: true, timerId: action.payload });
    },
    stopSuccess: (state/*, action*/) => {
      return Object.assign({}, state, { started: false, timerId: null });
    },
    // remember that actions generated with strings in the 'actions'
    // definition will not generate start/success/fail actions, so
    // in this case, 'increaseSuccess' and increaseFail will not work and
    // will never be called.
    increase: (state/*, action*/) => {
      return Object.assign({}, state, { count: state.count + 1});
    },
    clear: (state /*, action*/) => {
      return Object.assign({}, state, { count: 0 });
    }
  }
};

// helper function to demonstrate async
function delay() {
  return new Promise(resolve=>setTimeout(resolve, 2000));
}

// export your model
export default new ReduxModel(timer);

Once you have your model, you plug it in your redux store (redux-thunk is required).

import { createStore, applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
//models
import timerModel from './models/timer';
import { combineReducers } from 'redux';
const reducers = combineReducers({
  [timerModel.name]: timerModel.reducer
});

const logger = createLogger();
const store = createStore(
    reducers,
    applyMiddleware(...[thunkMiddleware, logger])
);

// make sure you init your model with the store!!
timerModel.init(store);

NOTICE: how the model is initialized right after the redux store is created.

Now you can consume your model from your containers or components.

import { connect } from 'react-redux'
import timerModel from '../../store/models/timer';
import HomeComponent from './HomeComponent';

const HomeContainer = connect(
  (state) => {
    return {
      started: state.timer.started,
      count: state.timer.count
    };
  },
  () => {
    return {
      onStart: () => timerModel.api.start(),
      onStop: () => timerModel.api.stop(),
      onClear: () => timerModel.api.clear(),
      onDelayStart: () => timerModel.api.delayStart()
    }
  }
)(HomeComponent);

export default HomeContainer;

Actions wills be generated as usual:

Helpers

There are two helper methods in case you have more than one model in your application.

combineModelReducers & initModels

You can arrange your models in an array or an object:

import user from './user';
import entries from './entries';
import comments from './comments';

export {
  user,
  entries,
  comments,
};

// could be export [ user, entries, comments]
import { createStore, applyMiddleware } from 'redux';
import createLogger from 'redux-logger';
import thunkMiddleware from 'redux-thunk';

// import the new helper functions
import { combineModelReducers, initModels } from 'redux-easy-models'

//models
import * as models from './models';

// combine all of the ReduxModel reducers
const reducers = combineModelReducers(models);

const logger = createLogger();
const store = createStore(
    reducers,
    applyMiddleware(...[thunkMiddleware, logger])
);

// init all the ReduxModels
initModels(models, store);

Credits

Thanks to Dan for his contributions of helper methods!

Keywords

flux

FAQs

Package last updated on 19 Aug 2016

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts