redux-advanced-subscribe
This module exports a decorator function that accepts a redux store instance as an argument and returns a new store with more advanced and easy-to-use subscribe functions to replace the raw subscribe
function provided by a Redux store. All subscribe functions return a function that can be called to unsubscribe that subscriber, returning true
on success or false
if the function had already been unsubscribed.
Example
import { createStore } from 'redux';
import injectSubscribe from 'redux-advanced-subscribe';
const store = injectSubscribe(createStore((state, action) => {
// some reducer
]));
const {
subscribeToState,
subscribeToStateOnce,
subscribeToKey,
subscribeToKeyOnce,
subscribeToEvent,
subscribeToEventOnce
} = store;
// set up subscribers
export default store;
Function descriptions
Store#subscribeToState
This is the core subscribe function that subscribes to any change in the state tree. The difference is that this function is passed the last state tree and the current state tree, so that you don't have to keep track of references manually.
const unsubscribe = store.subscribeToState((lastTree, currentTree) => {
if (lastTree.someBranch !== currentTree.someBranch) {
store.dispatch({ type: 'SOME_EVENT' });
}
});
Store#subscribeToStateOnce
This function subscribes to state changes and automatically unsubscribes after it has been called once.
Store#subscribeToKey
This function takes two arguments, the first argument can be a string representing one key in the state tree on which to listen for changes. If the first argument is a string, the second argument will be a function that will be called whenever that branch of the state tree changes, called with the last branch of the state tree by that key and the current one as arguments. If, however, the first argument to the function is an array, the second argument must be a function that will be called with the last entire state tree then the current state tree passed in as arguments.
const unsubscribe = store.subscribeToKey('someBranch', (lastSomeBranch, currentSomeBranch) => {
// do something with last branch and new branch
});
const otherUnsubscribe = store.subscribeToKey(['someBranch', 'someOtherBranch'], (lastTree, currentTree) => {
// do something when either someBranch or someOtherBranch has changed
});
Store#subscribeToKeyOnce
This function has the same behavior as Store#subscribeToKey
but will be unsubscribed automatically after the function is called once.
Store#subscribeToEvent
This function takes an event name as its first argument and the second argument will be a function that will be called with the last state tree and the current state tree as arguments whenever that event is dispatched.
const unsubscribe = store.subscribeToEvent('SOME_EVENT', (lastTree, currentTree) => {
// do something with state when SOME_EVENT is dispatched
});
Store#subscribeToEventOnce
This function has the same behavior as Store#subscribeToEvent
except it is unsubscribed after the function is called once.
Store#dispatch.mutexDispatch
This function is a property of Store#dispatch
and calls the dispatch
function with a mutex that ensures that no subscribers will be called as a result of the dispatch. This can be used to avoid infinite recursion in some cases when you only need your reducers to be called and are sure you do not need subscriber side effects, or it can be used for performance reasons when you do not want your subscribers to be executed.
Note: mutexDispatch
is a property of dispatch
primarily because this enables the function to be accessed from a react-redux
container in the mapDispatchToProps
function.
store.dispatch.mutexDispatch({
type: 'SOME_EVENT',
payload: // some payload
});
Motivation & Design Patterns
Manually keeping track of references to the last and current state tree in each of your subscribers can result in ugly code and can be a source of infinite recursion bugs, notably when calling dispatch
from within subscribers. These subscribers handle this mechanism for you.
Store#subscribeToEvent
can be a clean way to launch actions with side-effects from a container component, while keeping your container a pure function of the redux state and dispatch function, and it also allows separation of your core application and network logic from your container. You might see a similar pattern in applications that make use of react-saga.
Example of a pure container that executes networked logic via an event subscriber:
// in SomeContainer.js
import SomeComponent from './SomeComponent';
import { connect } from 'react-redux';
export default connect(({ someBranch }) => ({ someBranch }), (dispatch) => ({
onClick() {
dispatch({
type: 'REQUEST_FETCH_DATA'
});
}
}));
// somewhere in store.js
subscribeToEvent('REQUEST_FETCH_DATA', () => {
fetchDataAsync((err, result) => {
dispatch({
type: 'DATA_FETCH_COMPLETE',
payload: result
});
});
});
Sometimes, you may find yourself repeatedly merging several state branches together to form a new model from which many components are rendered. If this part of the state tree is large, or the same model is calculated in many components, then it can be extremely expensive to recalculate this model on every dispatch for every component. The solution is to use a computed branch of the state tree. Usage will vary, but as an example, if your computed branch is a combination of two arrays, you might create a reducer for a computed branch that responds to a 'PUSH_COMPUTED_ITEMS'
event and use Store#subscribeToKey
to subscribe to changes in the primary list, find the array difference of that branch of the current tree and last tree, calculate your new items, and dispatch a 'PUSH_COMPUTED_ITEMS'
event. More examples to come.
Author
Raymond Pulver IV
License
MIT