Home

Awesome

Modular Redux Thunk

Dependency Status npm version Build Status

A ducks-inspired package to help organize actions, reducers, and selectors together - with built-in redux-thunk support for async actions.

Rules

Installation

npm install --save modular-redux-thunk

import createStore from 'modular-redux-thunk';

const { store, pickActions, selectors } = createStore(myModules);

You can also include custom reducers, middleware, or enhancers. For example, if you install react-router and redux-freeze:

npm install --save react-router
import createStore from 'modular-redux-thunk';
import { routerReducer } from 'react-router-redux';

const { store, pickActions, selectors } = createStore(myModules, {
	reducers: {
		routing: routerReducer
	},
	enhancers: []
});

Example

Let's say your app will be storing the following information in the state:

reducers/chips.js

const actions = {};
const reducers = {};
const selectors = {};
const ACTION_PREPEND = 'my-react-app/chips';

const SET_FAVORITE_CHIPS = `${ACTION_PREPEND}/SET_FAVORITE_CHIPS`;
reducers.favorite = (state = 'unknown', action) => {
  switch(action.type) {
    case SET_FAVORITE_CHIPS: return action.newFav;
    default: return state;
  };
};
actions.setFavoriteChips = (newFav) => {
  return {
    type: SET_FAVORITE_CHIPS,
    newFav
  };
};
selectors.getFavoriteChips = (chipsState) => chipsState.favorite;

const SET_CHIPS_FOR_SALE = `${ACTION_PREPEND}/SET_CHIPS_FOR_SALE`;
reducers.chipsForSale = (state = [], action) => {
  switch(action.type) {
    case SET_CHIPS_FOR_SALE: return action.chips;
    default: return state;
  };
};
actions.setChipsForSale = (chips) => {
  return {
    type: SET_CHIPS_FOR_SALE,
    chips
  };
};
selectors.getChipsForSale = (chipsState) => chipsState.chips;

export default { actions, reducers, selectors };

reducers/drinks.js

import { combineModules, settableValue } from 'modular-redux-thunk';
const actions = {};
const reducers = {};
const selectors = {};
const ACTION_PREPEND = 'my-react-app/drinks';

// You can also define individual modules and combine them
const SET_FAVORITE_DRINK = `${ACTION_PREPEND}/SET_FAVORITE_DRINK`;
const favorite = {
	reducer: (state = 'unknown', action) => {
	  switch(action.type) {
	    case SET_FAVORITE_DRINK: return action.newFav;
	    default: return state;
	  };
	},
	actions: {
		setFavoriteDrink: (newFav) => ({
	    type: SET_FAVORITE_DRINK,
	    newFav
	  })
	},
	selectors: {
		getFavoriteDrink: (favoriteDrinkState) => favoriteDrinkState;
	}
}

// Or even use a module creator function to automate this common pattern
const drinksForSale = settableValue([], 'getDrinksForSale', 'setDrinksForSale');

export default combineModules({
	favorite,
	drinksForSale
});

reducers/selectors.js

export const getUserFavorites = (selectors, state) => {
  return {
    chips: selectors.getFavoriteChips(state),
    drink: selectors.getFavoriteDrink(state)
  }
};

reducers/actions.js

export const setUserFavorites = (actions, favChips, favDrink) => {
  return function(dispatch) {
    dispatch(actions.setFavoriteChips(favChips));
    dispatch(actions.setFavoriteDrink(favDrink));
  };
};

reducers/index.js

import createStore from 'modular-redux-thunk';

import chips from './chips.js';
import drinks from './drinks.js';
import * as globalActions from './actions.js';
import * as globalSelectors from './selectors.js';

const modules = {chips, drinks};

const globals = {
  globalActions: globalActions,
  globalSelectors: globalSelectors
};

const { store, selectors, pickActions } = createStore(modules, globals);
export { store, selectors, pickActions };

app.js

import React from 'react';
import { Provider, connect } from 'react-redux';
import { store, selectors, pickActions } from './reducers';

// Create the connected component
class _AppComponent extends React.Component {
  render() {
    const { favorites } = this.props;

    const statement = `My favorite kind of chips are ${favorites.chips} and drink is ${favorites.drink}!`;
    return (<div>{ statement }</div>);
  }
};
_AppComponent.propTypes = {
  setFavoriteChips: React.PropTypes.func,
  favorites: React.PropTypes.object
};
const AppComponent = connect(state => {
  return {
    favorites: selectors.getUserFavorites(state)
  };
}, pickActions('setFavoriteChips'))(_ChipsComponent);

const AppWrapper = (props) => {
	return (<Provider store={ store }><AppComponent /></Provider>);
};

ReactDOM.render(
  <AppWrapper />,
  document.getElementById('app')
);

API Reference

Module

A module object consists of:

createStore(modules, [preloadedState], [globalDefinitions], [reduxConfig])

Creates a Redux store that combines your reducers into a single and complete state tree.

Arguments

  1. modules (object): Defines the global structure of the store. Each key represents the modules's location in the store, and the value is the module object itself. Module objects are described above.
  2. [preloadedState] (object): Initial state passed to Redux.
  3. [globalDefinitions] (object): Pass in any global actions or selectors. Globals are given access to all reducers. You can pass in the following keys:
    • [globalActions] (object): Actions that can themselves perform actions from any reducer. Global actions differ from reducer actions in that the first argument will always be:
      • combinedActions (object): All combined actions from reducers. This allows you to reference reducer-defined actions.
    • [globalSelectors] (object): Selectors that have access to all reducer-defined selectors. Global selectors differ from reducer selectors in that the first argument will always be:
      • combinedSelectors (object): All combined selectors from reducers. This allows you to reference reducer-defined selectors.
  4. [reduxConfig] (object): Any custom redux config. You can pass in the following keys:
    • [reducers] (object): Additional reducers you'd like to be added to the store. For example, if using react-router, you can pass in routing which will be added to the store.
    • [middleware] (array): Any custom middleware to be added to the store. redux-thunk is automatically included as a middleware for your convenience.
    • [enhancers] (array): Any custom enhancers to be added to the store, such as redux-freeze. When not in production, redux-devtools-extension is automatically added for your convenience.

Returns

Returns an object with the following properties:

  1. store (Store): A Redux store that lets you read the state, dispatch actions
  1. pickActions (function): A function that returns an object of desired actions out of all available actions. Use this instead of passing the actions object to your connected components.
  2. selectors (object): All combined selectors for use when connecting your components.
  3. actions (object): All available actions. You can cherry-pick actions here as opposed to using pickActions.

combineModules(modules)

Takes a map of Module objects and returns a single Module object.

settableValue(initialValue, selectorName, actionName, [actionType])

Creates a module that controls a single value and responds to a single "set" action, as is quite common in Redux.

Arguments

  1. initialValue - The initial value of the module
  2. selectorName - The name of the module's single selector - usually something like getMyValue
  3. actionName - The name of the module's single action creator - usually something like setMyValue
  4. actionType - Optional. The action's type constant - usually something like SET_MY_VALUE. If not set, it will default to actionName.

TODO

Releasing

Commit all changes
npm run build # runs "npm test && npm run clean:build && npm run build && npm run test:build"
npm version "v1.0.0-beta1" -m "Message"
npm publish
git push origin HEAD:master --tags
# Update Changelog