3

My redux store is fairly large; Redux Devtools suggests sanitizing my larger objects to improve performance.

I've followed the docs here: https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md#excessive-use-of-memory-and-cpu

I've tried a number of combinations here, but none have given me the output I expect.

The current version, seen below, results in state being returned as a function, not an object. I know I'm doing something wrong, but I'm not sure what. Any guidance would be deeply appreciated.

Here's my store.js:


    'use strict'
    // libraries
    import { createStore, applyMiddleware, compose } from 'redux'

    // middleware
    import logger from 'redux-logger'
    import thunk from 'redux-thunk'

    // reducers
    import reducer from './reducers'

    const withLogger = false ? (thunk, logger) : thunk

    const isProd = process.env.NODE_ENV === 'production'

    const middleware = isProd ? thunk : withLogger

    const composeEnhancers = isProd
      ? compose
      : window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

    // sanitizers to keep redux devtools from using excessive memory
    const actionSanitizer = action =>
      !!action.id
      && action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
        ? { ...action, data: '<<LONG_BLOB>>' }
        : action

    const store = createStore(
      reducer,
      composeEnhancers(applyMiddleware(middleware)),


// The addition of this code breaks my store

      window.__REDUX_DEVTOOLS_EXTENSION__
        && window.__REDUX_DEVTOOLS_EXTENSION__({
          actionSanitizer,
          stateSanitizer: state =>
            state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
        })

// End breaking code

    )

Second try

I've made a couple of updates, and can now see the sanitizers' effect in devtools - depending on placement in my createStore function. Unfortunately this changes my composeEnhancers behavior (fires, or does doesn't fire depending on placement)


// middleware with or without logger
const middlewareEnhancer =
  true || ENV === 'production' // change to false to prevent logger output
    ? applyMiddleware(thunk, logger)
    : applyMiddleware(thunk)

// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
  !!action.id
  && action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
    ? { ...action, data: '<<LONG_BLOB>>' }
    : action

// compose
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(middlewareEnhancer) ||
  compose(middlewareEnhancer)

const store = createStore(
  // createStore signature > reducer, preLoadedState, enhancer
  rootReducer,
  // devtools extension works when I place it here per the examples in docs
  // BUT composed enhancers fail
  // Obviously, since the format wouldn't match the createStore signature
  // I have no idea how `__REDUX_DEVTOOLS_EXTENSION__` should be used in conjunction with composeEnhancers

  undefined,
  composeEnhancers,

  // devtools extension fails when placed here
  // composed enhancers run

  window.__REDUX_DEVTOOLS_EXTENSION__
    && window.__REDUX_DEVTOOLS_EXTENSION__({
      actionSanitizer,
      stateSanitizer: state =>
        state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
    })

)

Finally, persistence ftw!

I hate giving up; figured it out after rereading all the documentation posted by @markerikson. Always read the docs :'(

This may not be of use to anyone using configureStore and Redux Toolkit, but I'm documenting it regardless.

My big mistake was that actionSanitizer and stateSanitizer are Devtools Extension options, and should be added as such. Feel a fool, but at least I won't forget it.

The only thing left to do is implement redux-devtools-extension to avoid using window.__SOMEFUNC__ as suggested by markerikson.

The actual solution:

'use strict'
// libraries
import { createStore, applyMiddleware, compose } from 'redux'

// middleware
import logger from 'redux-logger'
import thunk from 'redux-thunk'

// reducers
import rootReducer from './reducers'

// middleware with or without logger
const middlewareEnhancer =
  true || ENV === 'production' // change to false to prevent logger output
    ? applyMiddleware(thunk, logger)
    : applyMiddleware(thunk)

// sanitizers to keep redux devtools from using excessive memory
const actionSanitizer = action =>
  !!action.id
  && action.type === `RECEIVE_${action.id.toString().toUpperCase()}_COLLECTION`
    ? { ...action, data: '<<LONG_BLOB>>' }
    : action

// compose
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({

    // add sanitizers here as devtools options
    // see https://github.com/zalmoxisus/redux-devtools-extension/tree/94f7e53800f4665bddc9b7438c5cc75cfb4547cc#12-advanced-store-setup
    // section 1.2

    actionSanitizer,
    stateSanitizer: state =>
      state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
  }) || compose

const enhancer = composeEnhancers(middlewareEnhancer)

const store = createStore(rootReducer, undefined, enhancer)

export default store

2 Answers2

1

As a first observation, this line seems wrong:

const withLogger = false ? (thunk, logger) : thunk

I'd strongly encourage you to first switch over to using the configureStore function from our official Redux Toolkit package, which handles the store setup process for you. From there, you can still pass DevTools configuration options to configureStore() if desired.

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • I use withLogger to hide/show logger middleware's console output. I usually leave it `false` because logger adds so much noise in the console. This setup works fine without the inclusion of the additional state sanitizer though, so the withLogger variable is not affecting my expected output (I will take a look at toolkit though, it certainly looks helpful) – Francois Carstens Jan 20 '20 at 17:23
  • I'm saying that the ternary statement looks wrong as currently written, particularly the `(thunk, logger)` part. At a minimum, I'd also suggest reading through the [Configuring Your Store](https://redux.js.org/recipes/configuring-your-store) page in the Redux docs for examples of store setup. – markerikson Jan 20 '20 at 17:24
  • Looks like I'm a version behind. I haven't read the docs in a while. Thanks for your help. I'll be back to update my post. – Francois Carstens Jan 20 '20 at 17:37
  • I'm not seeing any mention of actionSanitizers in the Redux 4x documentation. I assume it's no longer necessary. It appears an update is in order. – Francois Carstens Jan 20 '20 at 18:15
  • No, `actionSanitizer` is [a Redux DevTools configuration option](https://github.com/zalmoxisus/redux-devtools-extension/blob/94f7e53800f4665bddc9b7438c5cc75cfb4547cc/docs/API/Arguments.md#actionsanitizer--statesanitizer), not a Redux option. My point is that your logic for overall store setup may be wrong in the first place. Make sure the basic store setup is correct, _then_ try passing the additional options to the DevTools. – markerikson Jan 20 '20 at 18:20
  • I've updated (added to the bottom) my store code. As far as I can tell, this follows the documentation example in /recipes (without configureStore). I'm still not sure where the devtools extension function needs to be placed. Either it works, or my composeEnhancers work, not both unfortunately. I really appreciate your time. – Francois Carstens Jan 20 '20 at 19:50
  • 1
    The DevTools Extension docs [show where to add configuration options for "manual" setup logic under the "Advanced Store Setup" heading](https://github.com/zalmoxisus/redux-devtools-extension/tree/94f7e53800f4665bddc9b7438c5cc75cfb4547cc#12-advanced-store-setup). But, as I've already said, the "manual" `window.WHATEVER` logic shown in the DevTools Extension is very hard to work with. Please use either the `redux-devtools-extension` NPM package or Redux Toolkit for that part, as shown in [Configuring Your Store](https://redux.js.org/recipes/configuring-your-store). Then pass in the options. – markerikson Jan 20 '20 at 19:54
  • I've been reluctant to add additional libraries, but I'm going to take your advice, there's a reason those exist. Again, thank you for your time. – Francois Carstens Jan 20 '20 at 19:57
  • I couldn't give up. Figured it out and added the solution to the original post. – Francois Carstens Jan 20 '20 at 20:30
0

Only to complete the answer for those using the redux toolkit, here is an example entry that works well for me.

const devToolsConfiguration = {

  actionSanitizer: (action) => {
    switch (true) {
      case action.type.includes(RESOLVED):
        return typeof action.payload !== 'undefined'
          ? { ...action, payload: '<<LONG_BLOB>>' }
          : { ...action, results: '<<LONG_BLOB>>' };
      /* ... more entries */
      default:
        return action;
    }
  },
  stateSanitizer: (state) =>
    state.data?.matrix
      ? { ...state, data: { ...state.data, matrix: '<<LONG_BLOB>>' } }
      : state,

};

I then reference the configuration in the toolkit's configureStore function:

  const store = configureStore({
    reducer: persistedReducer,
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        thunk: false,
        serializableCheck: false,
        immutableCheck: false,
      }).prepend(middlewares),
    preloadedState: initialState,
    devTools: devToolsConfiguration,  // <<< here
  });
Seth Lutske
  • 9,154
  • 5
  • 29
  • 78
Edmund's Echo
  • 766
  • 8
  • 15