Using non-serializable values in Redux store

Loading

A common Redux anti-pattern is storing non-serializable values in the Redux store, which can lead to issues with time-travel debugging, persistence, and general maintainability. Here’s how to properly handle complex data in Redux.

Why Serializability Matters

  1. Debugging: Redux DevTools relies on serializable actions and state
  2. Persistence: Needed for saving state to storage and rehydrating
  3. Predictability: Ensures consistent behavior across environments
  4. Middleware: Many Redux middleware expect serializable data

Problematic Data Types to Avoid

❌ Class instances
❌ Functions/Promises
❌ Symbols
❌ Maps/Sets
❌ React components
❌ Circular references
❌ Native DOM elements

Solutions for Common Cases

1. Handling Class Instances

Instead of:

// ❌ Bad - storing class instance
state.user = new User(userData);

Do:

// ✅ Good - store plain objects
state.user = userData;

// If needed, reconstruct when selecting
const selectUser = (state) => new User(state.user);

2. Dealing with Functions

Instead of:

// ❌ Bad - storing function in state
state.onComplete = () => console.log('Done!');

Do:

// ✅ Good - use action types/thunks
// Dispatch this action when complete
dispatch({ type: 'TASK_COMPLETED' });

3. Managing Dates

Instead of:

// ❌ Bad - Date object isn't fully serializable
state.lastUpdated = new Date();

Do:

// ✅ Good - store as ISO string
state.lastUpdated = new Date().toISOString();

// Convert back when needed
new Date(state.lastUpdated);

4. Handling Complex Objects

Instead of:

// ❌ Bad - Map instance
state.items = new Map([[1, 'a'], [2, 'b']]);

Do:

// ✅ Good - convert to plain object/array
state.items = { 1: 'a', 2: 'b' };
// or
state.items = [[1, 'a'], [2, 'b']];

Redux Toolkit Best Practices

Redux Toolkit’s configureStore automatically checks for non-serializable values:

import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        // Ignore these action types
        ignoredActions: ['some/non-serializable'],
        // Ignore these paths in the state
        ignoredPaths: ['items.dates'],
      },
    }),
});

When You Must Store Non-Serializable Data

For legitimate cases where you need non-serializable values:

  1. Document why it’s necessary
  2. Configure middleware to ignore those paths
  3. Consider alternative approaches:
  • Store in component state instead
  • Use React context
  • Utilize a specialized library (like Redux-ORM)

Migration Strategy

  1. Audit your store for non-serializable values
  2. Refactor incrementally – start with most problematic areas
  3. Add serializability checks to catch new issues
  4. Educate your team about the importance

Debugging Non-Serializable Values

Redux Toolkit will log warnings in development. You can also add custom checks:

function checkSerializable(value) {
  try {
    JSON.stringify(value);
    return true;
  } catch (e) {
    console.warn('Non-serializable value:', value);
    return false;
  }
}

By maintaining serializable state, you’ll ensure your Redux store works reliably with all the ecosystem tools and follows established best practices.

Leave a Reply

Your email address will not be published. Required fields are marked *