redux-filter-subscriptions-enhancer
Overview:
- This project contains a Redux "Store Enhancer"
- Its purpose is two-fold:
-
store.subscribe(listener, filter)
- adds a new (optional) input parameter:
filter
- its purpose is to conditionally filter when
listener
is called - when its value is:
-
function
:- input: (oldState, newState)
- output: boolean
- when truthy,
listener
is not called for this state change
- when truthy,
-
string
:- treated as a jsonpath pathExpression
- precondition: state must be an Object
- will throw an Error when the root reducer returns a state that's not an Object
- pathExpression uniquely defines the path to one specific data structure within the state Object
- precondition: state must be an Object
-
filter
looks up the value of this data structure in botholdState
andnewState
- these values are compared for equality (by reference)
- if equal,
listener
is not called for this state change
- if equal,
- these values are compared for equality (by reference)
- treated as a jsonpath pathExpression
-
boolean
:- if
true
:-
oldState
andnewState
are compared for equality (by reference)- if equal,
listener
is not called for this state change- note: this is optimized but functionally equivalent to the jsonpath pathExpression: "$"
- if equal,
-
- if
-
undefined
|false
| any other value:- default behavior
-
listener
is always called after the root reducer function completes processing a dispatched action
-
- default behavior
-
- its purpose is to conditionally filter when
- adds a new (optional) input parameter:
-
listener(newState, action)
- passes information to the
listener
-
newState
is the same value returned bystore.getState()
-
action
is the value received bystore.dispatch()
that caused the reducer functions to change state- it's debatable whether there's any reason a listener should ever use this value
- it's officially considered by the Redux team to be an anti-pattern
- but..
- one-size-fits-all rules don't work for every situation
- there may be a valid reason to want this value
- if a project is using middleware to debounce actions
- it is the author's responsibility to be aware of this fact
- imho..
- better to have the data available without any reason to refer to it,
than to have the data unavailable and being in the rare situation that it is needed
- better to have the data available without any reason to refer to it,
-
- passes information to the
-
Background:
- I submitted a pull-request to the Redux repo with a minimal patch to add the enhancement:
store.subscribe(listener, filter)
- It got turned down flat:
- "We have no plans to implement this. If you'd like to do it yourself, you can implement it as a store enhancer."
- So.. I did, and here we are
Installation:
npm install --save '@warren-bank/redux-filter-subscriptions-enhancer'
Usage Example #1:
- This calling pattern allows the
middleware
enhancer to modify the base redux store before our enhancer - The result is that calls to
store.dispatch(action)
pass through:- our enhancer
- all middleware
- base redux store
- all middleware
- our enhancer
- The effect is that the action passed to
listener
has not been modified bymiddleware
- in most cases, this would be undesirable
-
listener
will most-likely want to receive the action exactly as it is received by the base redux store
const { applyMiddleware, createStore } = require('redux')
const enhancer = require('@warren-bank/redux-filter-subscriptions-enhancer')
const rootReducer = (state, action) => (action.type === "test") ? state + 1 : state
const initialState = 0
const middleware = applyMiddleware(logger, thunk, etc)
const store = enhancer(createStore)(rootReducer, initialState, middleware)
const listener = (newState, action) => console.log(JSON.stringify({newState, action}, null, 2))
const filter = (oldState, newState) => newState % 2 !== 0
store.subscribe(listener, filter)
rundown:
- the
state
is an integer that is initialized to0
, and its value increments by1
every time a "test" action is dispatched to the store - when
listener
is called, it logs its input parameters to the console as a serialized Object containing descriptive keys - when
listener
subscribes to the store, it includes afilter
function that will apply custom conditional logic to determine whenlistener
should be called- in this case,
listener
wants to be informed each time the state changes to an even value
- in this case,
reminder:
- unlike the filter function for an Array, which returns true to include values..
- this function will block calls to
listener
when the filter function returns true
to run tests containing code similar to usage example #1:
- in node.js:
git clone "https://github.com/warren-bank/redux-filter-subscriptions-enhancer.git"
cd "redux-filter-subscriptions-enhancer"
cd "browser-build"
npm install
cd "../tests"
npm install
npm run test
- in the web browser:
Usage Example #2:
- This calling pattern is effectively identical to usage example #1
- but it opens the door..
const { applyMiddleware, compose, createStore } = require('redux')
const enhancer = require('@warren-bank/redux-filter-subscriptions-enhancer')
const rootReducer = (state, action) => (action.type === "test") ? state + 1 : state
const initialState = 0
const middleware = applyMiddleware(logger, thunk, etc)
const rootEnhancer = compose(enhancer, middleware)
const store = createStore(rootReducer, initialState, rootEnhancer)
const listener = (newState, action) => console.log(JSON.stringify({newState, action}, null, 2))
const filter = (oldState, newState) => newState % 2 !== 0
store.subscribe(listener, filter)
Usage Example #3:
- This calling pattern reverses the order, and allows our enhancer to receive dispatched actions after they have been processed by
middleware
const { applyMiddleware, compose, createStore } = require('redux')
const enhancer = require('@warren-bank/redux-filter-subscriptions-enhancer')
const rootReducer = (state, action) => (action.type === "test") ? state + 1 : state
const initialState = 0
const middleware = applyMiddleware(logger, thunk, etc)
const rootEnhancer = compose(middleware, enhancer)
const store = createStore(rootReducer, initialState, rootEnhancer)
const listener = (newState, action) => console.log(JSON.stringify({newState, action}, null, 2))
const filter = (oldState, newState) => newState % 2 !== 0
store.subscribe(listener, filter)
to run tests containing code similar to usage examples #2 and #3:
- in node.js:
git clone "https://github.com/warren-bank/redux-filter-subscriptions-enhancer.git"
cd "redux-filter-subscriptions-enhancer"
cd "browser-build"
npm install
cd "../tests"
npm install
npm run test
- in the web browser:
Browser Build (transpiled to ES5):
-
files in repo:
-
files hosted in CDN:
-
global variable(s):
- window.redux_filter_subscriptions_enhancer
-
conditional dependencies:
-
jsonpath is not bundled into the browser build
- it was briefly in v2.0.0
- the size increased from 1.06 KB (v1.0.0) to 164 KB (v2.0.0)
- support for jsonpath filters is now an optional feature
- to enable this feature:
- include the additional browser build script: jsonpath
- to use it:
- call
store.subscribe(listener, filter)
- assign to
filter
a string value containing a well-formatted pathExpression
- assign to
- example:
- call
- it was briefly in v2.0.0
-
jsonpath is not bundled into the browser build
Legal:
- copyright: Warren Bank
- license: GPL-2.0