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

v0.0.4

Published

Easily create models using standard redux architecture.

Downloads

3

Readme

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!