DataDog/redux-doghouse

Name: redux-doghouse

Owner: Datadog, Inc.

Description: Scoping helpers for building reusable components with Redux

Created: 2016-10-07 15:00:27.0

Updated: 2018-05-20 08:20:22.0

Pushed: 2016-11-22 21:28:31.0

Homepage:

Size: 89

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

redux-doghouse

redux-doghouse is a library that aims to make reusable components easier to build with Redux by scoping actions and reducers to a particular instance of a component.

A diagram of how Redux-Doghouse works

It includes tools to help you build Scoped Actions and Scoped Reducers with minimal modifications to your code. That way, if you build a Redux store for a Parent with an arbitrary number of Children (meaning there can be none, one, or a million of them), actions affecting Child A won't affect Child B through Child Z.

You can read more about why we built redux-doghouse, and our real-world use-case, in this blog post.

Getting Started

Installation
install redux-doghouse
Running Examples
Counters (Live Demo)

An app that renders an arbitrary number of <Counter>s, with the ability to change the value of one of them at a time, all of them at once, or only the ones with even or odd numbers

run counters

API

  1. scopeActionCreators
  2. ScopedActionFactory
  3. bindScopedActionFactories
  4. bindActionCreatorsDeep
  5. scopeReducers
For Actions
scopeActionCreators(actionCreators, scopeID)

Adds a scope to the output of a set of action creators

The actionCreators should be in the same format that you would pass to bindActionCreators. e.g:

ue) => ({type: SET_FOO, value})

or


foo: (value) => ({type: SET_FOO, value})

It will then scope each of these action creators, so that the resulting action will include the scopeID.


foo: (value) => ({
    type: SET_FOO, value,
    scopeID: "[the specified scopeID]"
})

Arguments Returns

(Object or Function): An object mimicking the original object, but with each function adding { scopeID } to the Object that they return. If you passed a function as actionCreators, the return value will also be a single function.

Example
rt { scopeActionCreators } from 'redux-doghouse';
rt { actionCreators } from './my-actions';

efore scoping:
onCreators.foo('bar');
ill return

type: 'SET_FOO',
value: 'bar'

After scoping:
eActionCreators(actionCreators, 'a').foo('bar')
ill return

type: 'SET_FOO',
value: 'bar',
scopeID: 'a'

ScopedActionFactory(actionCreators)

Works similarly to scopeActionCreators, but with the added benefit of instanceof checking. This allows you to write a check to see whether or not a set of action creators is an instanceof ScopedActionFactory.

For example, the included bindActionCreatorsDeep function will intelligently bind an object tree of both scoped and un-scoped action creators, depending on whether it's passed plain objects or ScopedActionFactory instances.

Arguments Returns

(ScopedActionFactory) A class of object with the following:

Instance Methods scope(id): Object

Runs scopeActionCreators(id) on the actionCreators that were passed to the new ScopeActionFactory, and returns the result.

Example
rt { ScopedActionFactory } from 'redux-doghouse';
rt { actionCreators } from './my-actions';

t scopeableActions = new ScopedActionFactory(actionCreators);
t actionCreatorsScopedToA = scopeableActions.scope('a');
t actionCreatorsScopedToB = scopeableActions.scope('b');

onCreatorsScopedToA.foo('bar')
ill return

type: SET_FOO,
value: 'bar',
scopeID: 'a'

bindScopedActionFactories(actionFactories, dispatch, [bindFn])

Takes an object of actionFactories and binds them all to a dispatch function. By default, it will use Redux's included bindActionCreators to do this, but you can specify a bindFn to use instead.

Arguments Returns

(Object or ScopedActionFactory): An object mimicking the original object, but with each ScopedActionFactory generating functions that will immediately dispatch the action returned by the corresponding action creator. If you passed a single factory as actionFactories, the return value will also be a single factory.

Example
rt { createStore } from 'redux';
rt { ScopedActionFactory, bindScopedActionFactories } from 'redux-doghouse';
rt { actionCreators, reducers } from './my-redux-component';

t store = createStore(reducers);
t scopeableActions = {
myComponentActions: new ScopedActionFactory(actionCreators);

t boundScopeableActions = bindScopedActionFactories(scopeableActions, store.dispatch);
bindActionCreatorsDeep(actionCreatorTree, dispatch)

Extends Redux's native bindActionCreators to allow you to bind a whole tree of nested action creator functions and ScopedActionFactory instances to a dispatch function.

Arguments Example
rt { bindActionCreatorsDeep, ScopedActionFactory } from 'redux-doghouse';
rt { createStore } from 'redux';
rt { reducers } from './my-reducers';

t store = createStore(reducers);

t actionCreators = {
fooActions: {
    bar: (value) => ({type: 'BAR', value})
},
barActions: {
    baz: new ScopedActionFactory({
        quux: (value) => ({type: 'QUUX'})
    })
}

t boundActionCreators = bindActionCreatorsDeep(boundActionCreators, store.dispatch);
For Reducers
scopeReducers(reducers)

This acts as an extension of Redux's combineReducers, which takes an object of reducers in the form of { [prop]: reducer(state, action) } pairs and combines them into a single reducer that returns { [prop]: state } pairs. scopeReducers goes a step further and returns an object of { [scopeID]: { [prop]: state} } pairs.

In other words, it will create a reference to your reducers for each new scopeID you add to your data model, and route scoped actions to their corresponding scopeID when reducing a new state.

Arguments Returns

(Function): A reducer that takes an object of state objects in the form of { [scopeID]: state } pairs, and an action that includes a scopeID. The reducer will return a new object mimicking the original object, but for each key:

  1. For the matching scopeID, invoke the reducers to construct a new state object with the same shape as the reducers
  2. Leave all the other scopeIDs' state objects unchanged
Example
iven these actionCreators...
rt { scopeActionCreators } from 'redux-doghouse';
t reducers = {
foo: (state = 0, action) => {
    switch (action.type) {
        case 'INCREMENT_FOO':
            return state + 1;
        case 'DECREMENT_FOO':
            return state - 1;
        default:
            return state;
    }
}

t actionCreatorsA = scopeActionCreators({
incrementFoo: () => ({type: 'INCREMENT_FOO'})
a');

ithout scoping
rt { combineReducers } from 'redux';
t combinedReducers = combineReducers(reducers);
t state = {foo: 0};
inedReducers(state, actionCreatorsA.incrementFoo());
ill return
: 1}

ith scoping
rt { scopeReducers } from 'redux-doghouse';
t scopedReducers = scopeReducers(reducers);
t state = {
a: {foo: 0},
b: {foo: 2}

edReducers(state, actionCreatorsA.incrementFoo());
ill return

a: {foo: 1},
b: {foo: 2}


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.