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.
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.
npm install redux-doghouse
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
npm run counters
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:
(value) => ({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]"
})
}
actionCreators
(Function or Object): An action creator, or an object whose values are action creators. The values can also be nested objects whose values are action creators (or more nested objects, and so on).
scopeID
(String or Number): An identifier to include in any actions created by the actionCreators
(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.
import { scopeActionCreators } from 'redux-doghouse';
import { actionCreators } from './my-actions';
// Before scoping:
actionCreators.foo('bar');
// Will return
{
type: 'SET_FOO',
value: 'bar'
};
/// After scoping:
scopeActionCreators(actionCreators, 'a').foo('bar')
// Will 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.
actionCreators
(Function or Object): An action creator, or an object whose values are action creators(ScopedActionFactory) A class of object with the following:
scope(id): Object
Runs scopeActionCreators(id)
on the actionCreators
that were passed to the new ScopeActionFactory
, and returns the result.
import { ScopedActionFactory } from 'redux-doghouse';
import { actionCreators } from './my-actions';
const scopeableActions = new ScopedActionFactory(actionCreators);
const actionCreatorsScopedToA = scopeableActions.scope('a');
const actionCreatorsScopedToB = scopeableActions.scope('b');
actionCreatorsScopedToA.foo('bar')
// Will 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.
actionFactories
(Object or ScopedActionFactory): A single ScopedActionFactory, or an object whose values are ScopedActionFactories.dispatch
(Function): A function to which the resulting action creators from actionFactories
should be dispatched; usually this is the dispatch
method of a Redux storebindFn
] (Function): If specified, this function will be used to bind resulting action creators to the dispatch
. If unspecified, Redux's native bindActionCreators
will be used by default.(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.
import { createStore } from 'redux';
import { ScopedActionFactory, bindScopedActionFactories } from 'redux-doghouse';
import { actionCreators, reducers } from './my-redux-component';
const store = createStore(reducers);
const scopeableActions = {
myComponentActions: new ScopedActionFactory(actionCreators);
}
const 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 function
s and ScopedActionFactory
instances to a dispatch
function.
actionCreatorTree
(Object): An object whose values can be:
dispatch
ScopedActionFactory
to be bound to the dispatch
dispatch
(Function): A function to which the members of actionCreatorTree
should be dispatched; usually this is the dispatch
method of a Redux storeimport { bindActionCreatorsDeep, ScopedActionFactory } from 'redux-doghouse';
import { createStore } from 'redux';
import { reducers } from './my-reducers';
const store = createStore(reducers);
const actionCreators = {
fooActions: {
bar: (value) => ({type: 'BAR', value})
},
barActions: {
baz: new ScopedActionFactory({
quux: (value) => ({type: 'QUUX'})
})
}
}
const boundActionCreators = bindActionCreatorsDeep(boundActionCreators, store.dispatch);
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.
reducers
(Object): An object whose keys correspond to property names, and whose values correspond to different reducing functions that need to be combined into one and reused across multiple scopes.(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:
scopeID
, invoke the reducers
to construct a new state object with the same shape as the reducers
scopeID
s' state objects unchanged// Given these actionCreators...
import { scopeActionCreators } from 'redux-doghouse';
const reducers = {
foo: (state = 0, action) => {
switch (action.type) {
case 'INCREMENT_FOO':
return state + 1;
case 'DECREMENT_FOO':
return state - 1;
default:
return state;
}
}
};
const actionCreatorsA = scopeActionCreators({
incrementFoo: () => ({type: 'INCREMENT_FOO'})
}, 'a');
// Without scoping
import { combineReducers } from 'redux';
const combinedReducers = combineReducers(reducers);
const state = {foo: 0};
combinedReducers(state, actionCreatorsA.incrementFoo());
// Will return
{foo: 1}
// With scoping
import { scopeReducers } from 'redux-doghouse';
const scopedReducers = scopeReducers(reducers);
const state = {
a: {foo: 0},
b: {foo: 2}
};
scopedReducers(state, actionCreatorsA.incrementFoo());
// Will return
{
a: {foo: 1},
b: {foo: 2}
}