Skip to content

vshushkov/redux-models

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Redux models

Build Test Coverage

Models layer for Redux. redux-models simplifies working with remote data (well.. not only remote) and helps to organize your code.

Installation

npm install --save redux redux-models

Usage

models/User.js
import { createModel } from 'redux-models';

export default createModel({
  name: 'User',
  methods: {
    findByUsername(username) {
      return fetch(`https://api.github.com/users/${username}`).then(res =>
        res.json()
      );
    }
  }
});
store.js
import { combineReducers, applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import User from './models/User';

export default createStore(
  combineReducers({
    ...User.reducers
  }),
  applyMiddleware(thunk)
);
app.js
import React from 'react';
import { connect } from 'react-redux';
import User from './models/User';

class UserAvatar extends React.Component {
  componentDidMount() {
    const { fetchUser } = this.props;
    fetchUser();
  }

  render() {
    const { user } = this.props;

    if (!user) {
      return <div>Loading...</div>;
    }

    return <img src={user.avatar_url} alt="avatar" />;
  }
}

export default connect(
  (state, { username }) => ({
    user: User(state).findByUsername(username)
  }),
  (dispatch, { username }) => ({
    fetchUser: () => dispatch(User.findByUsername(username))
  })
)(UserAvatar);

Live demo

API

createModel(options)

Arguments

options:

  • options.name: (String): Name of a model
  • options.mixins: (Array): Array of mixins
  • options.methods: (Object): Model's methods
  • options.reducer: (Function [optional]): Model reducer.
  • options.typePrefix: (String [optional]): Prefix of actions types. Default @@redux-models.
  • options.modelState: (Function [optional]): Function to map state to model state. Default state => state[{ model name }].

Returns

Newly created model with defined methods. Each model method creates action to dispatch.

Model reducer

Additional data processing from the methods can be done in the model reducer.

Model reducer arguments are same as redux reducers, except the last argument types. It contains all action types strings your model can dispatch (including mixins action types). In following example model User has one method find and it can dispatch actions with types: @@redux-models/USER/FIND, @@redux-models/USER/FIND_SUCCESS, @@redux-models/USER/FIND_ERROR, @@redux-models/USER/FIND_RESET, so types contains object:

{
  FIND: '@@redux-models/USER/FIND',
  find: '@@redux-models/USER/FIND',
  FIND_SUCCESS: '@@redux-models/USER/FIND_SUCCESS',
  findSuccess: '@@redux-models/USER/FIND_SUCCESS',
  FIND_ERROR: '@@redux-models/USER/FIND_ERROR',
  findError: '@@redux-models/USER/FIND_ERROR',
  FIND_RESET: '@@redux-models/USER/FIND_RESET',
  findReset: '@@redux-models/USER/FIND_RESET'
}

After processing, the data will be available in state.{ model name }.model.

Example

import { createModel } from 'redux-models';

const defaultState = {
  byId: {}
};

export default createModel({
  name: 'User',
  methods: {
    find(query) {
      // async request
    }
  },
  reducer(state = defaultState, action, { findSuccess }) {
    const { type, payload: { result } = {} } = action;

    if (type === findSuccess) {
      return {
        ...state,
        byId: {
          ...state.byId,
          ...(result || []).reduce(
            (byId, user) => ({
              ...byId,
              [user.id]: user
            }),
            {}
          )
        }
      };
    }

    return state;
  }
});

Then:

import { connect } from 'react-redux';
// ...

export default connect((state, { id }) => ({
  user: state.User.model.byId[id]
}))(UserCard);

Mixins

Mixins allow you to add method sets to multiple models. For example mixin crud adds methods: create, updateById, deleteById, find, findById.

crud.js
import createMixin from 'redux-models-mixin-crud';

export default function crudMixin(path) {
  return createMixin({ 
    methods: {
      create() { /*...*/ },
      updateById() { /*...*/ },
      deleteById() { /*...*/ },
      find() { /*...*/ },
      findById() { /*...*/ }
    } 
  });
}
book.js
import { createModel } from 'redux-models';
import crudMixin from './crud';

export default createModel({
  name: 'Book',
  mixins: [crudMixin('/books')]
});

Contributing

See the Contributors Guide

License MIT