Skip to content
redux store enhancer: `store.subscribe(listener, filter)` -> `listener(newState, action)`
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
browser-build
tests
.babelrc
.gitignore
.npmignore
LICENSE.txt
README.md
index.js
package-lock.json
package.json

README.md

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
          • 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
            • filter looks up the value of this data structure in both oldState and newState
              • these values are compared for equality (by reference)
                • if equal, listener is not called for this state change
          • boolean:
            • if true:
              • oldState and newState 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: "$"
          • undefined | false | any other value:
            • default behavior
              • listener is always called after the root reducer function completes processing a dispatched action
    • listener(newState, action)
      • passes information to the listener
        • newState is the same value returned by store.getState()
        • action is the value received by store.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

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
  • The effect is that the action passed to listener has not been modified by middleware
    • 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 to 0, and its value increments by 1 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 a filter function that will apply custom conditional logic to determine when listener should be called
    • in this case, listener wants to be informed each time the state changes to an even value

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:

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

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:

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

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
        • example:

Legal:

You can’t perform that action at this time.