Redux DevTools Setup

Introduction to Redux DevTools

Redux DevTools is a powerful debugging tool that allows you to inspect every state change in your application, travel through time, and debug your Redux application effectively.

🎬 The Movie Production Analogy

Think of Redux DevTools like a movie production studio:

  • State History: Recording all scenes (actions) of your movie
  • Time Travel: The ability to rewind and replay any scene
  • Action Replay: Playing back individual scenes to see what happened
  • State Diff: Seeing changes between each take
  • Action Dispatcher: The director calling "Action!" for any scene
  • Export/Import: Sharing your movie script with other directors

Just as movie directors need to review and edit scenes, developers need to review and debug application state changes.

Installing Redux DevTools

1. Browser Extension Installation


// Step 1: Install the browser extension
// - Chrome: Redux DevTools Extension from Chrome Web Store
// - Firefox: Redux DevTools Add-on from Firefox Add-ons
// - Edge: Redux DevTools from Microsoft Edge Add-ons

// Step 2: Basic setup in your Redux store
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(
  rootReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

// With middleware
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(
  rootReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ 
    ? window.__REDUX_DEVTOOLS_EXTENSION__()
    : applyMiddleware(thunk)
);
            

2. Advanced Setup with Compose


import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducers';

// Check if DevTools extension is installed
const composeEnhancers = 
  (typeof window !== 'undefined' && 
   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || 
  compose;

// Create store with middleware and DevTools
const store = createStore(
  rootReducer,
  composeEnhancers(
    applyMiddleware(thunk, logger)
  )
);

// With configuration options
const composeEnhancers =
  (typeof window !== 'undefined' &&
   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
     // Specify extension options
     name: 'MyApp',
     trace: true,
     traceLimit: 25,
   })) ||
  compose;
            

3. Redux Toolkit Setup


import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';

// Redux Toolkit automatically configures DevTools
const store = configureStore({
  reducer: rootReducer,
  // DevTools enabled by default in development
  devTools: process.env.NODE_ENV !== 'production',
});

// With custom DevTools configuration
const store = configureStore({
  reducer: rootReducer,
  devTools: {
    name: 'MyApp',
    trace: true,
    traceLimit: 25,
    // Custom action sanitizer
    actionSanitizer: (action) => {
      if (action.type === 'FILE_UPLOAD') {
        return { ...action, payload: '<>' };
      }
      return action;
    },
    // Custom state sanitizer
    stateSanitizer: (state) => {
      if (state.auth && state.auth.token) {
        return {
          ...state,
          auth: {
            ...state.auth,
            token: '<>'
          }
        };
      }
      return state;
    },
    // Features configuration
    features: {
      pause: true, // Start/pause recording of dispatched actions
      lock: true, // Lock/unlock dispatching actions
      persist: true, // Persist states on page reloading
      export: true, // Export history of actions in a file
      import: 'custom', // Import history of actions from a file
      jump: true, // Jump back and forth (time travel)
      skip: true, // Skip (cancel) actions
      reorder: true, // Drag and drop actions in the history list
      dispatch: true, // Dispatch custom actions
      test: true // Generate tests for the selected actions
    }
  }
});
            

DevTools Features

graph TD A[Redux DevTools] --> B[Inspector] A --> C[Log Monitor] A --> D[Dispatcher] A --> E[State] A --> F[Diff] A --> G[Chart] A --> H[Test Generator] B --> B1[Action List] B --> B2[Action Details] B --> B3[State Tree] D --> D1[Custom Actions] D --> D2[Action Templates] E --> E1[Raw State] E --> E2[State Chart] E --> E3[State Map] style A fill:#f96 style B fill:#9cf style D fill:#9cf style E fill:#9cf

1. Inspector Panel


// The Inspector shows:
// - List of dispatched actions
// - Action payload details
// - State after each action
// - Time of dispatch

// Example action in DevTools
{
  type: 'todos/todoAdded',
  payload: {
    id: '123',
    text: 'Learn Redux DevTools',
    completed: false
  },
  meta: {
    timestamp: 1678901234567
  }
}

// State tree view
{
  todos: {
    ids: ['123'],
    entities: {
      '123': {
        id: '123',
        text: 'Learn Redux DevTools',
        completed: false
      }
    }
  },
  ui: {
    filter: 'all'
  }
}
            

2. Time Travel Debugging


// Time travel allows you to:
// - Jump to any previous state
// - Replay actions in sequence
// - Skip specific actions
// - See state at any point in time

// How it works internally
const timeTravelMiddleware = store => next => action => {
  // Before action
  const prevState = store.getState();
  
  // Execute action
  const result = next(action);
  
  // After action
  const nextState = store.getState();
  
  // Record state change
  devToolsHistory.push({
    action,
    prevState,
    nextState,
    timestamp: Date.now()
  });
  
  return result;
};

// DevTools can then replay or skip actions
function replayActions(actions, initialState) {
  return actions.reduce((state, action) => {
    return rootReducer(state, action);
  }, initialState);
}
            

3. Action Dispatcher


// Manual action dispatch from DevTools
// Use the Dispatcher tab to send custom actions

// Example dispatched action
{
  type: 'ADD_TODO',
  payload: {
    text: 'Test from DevTools',
    id: Date.now()
  }
}

// Action templates for common testing
const actionTemplates = {
  addUser: {
    type: 'users/userAdded',
    payload: {
      id: '{{random.uuid}}',
      name: '{{name.firstName}} {{name.lastName}}',
      email: '{{internet.email}}',
      createdAt: new Date().toISOString()
    }
  },
  
  updateSettings: {
    type: 'settings/updated',
    payload: {
      theme: 'dark',
      notifications: true,
      language: 'en'
    }
  },
  
  apiError: {
    type: 'api/requestFailed',
    payload: {
      endpoint: '/api/users',
      error: 'Network Error',
      status: 500
    },
    error: true
  }
};
            

4. State Diff View


// DevTools shows state differences between actions
// Before action:
{
  todos: [
    { id: 1, text: 'Learn Redux', completed: false }
  ],
  filter: 'all'
}

// After action (todos/todoCompleted):
{
  todos: [
    { id: 1, text: 'Learn Redux', completed: true }  // Changed
  ],
  filter: 'all'
}

// Diff view shows:
// - Changed: todos[0].completed: false → true
// - Unchanged: filter
            

Advanced DevTools Configuration

1. Action and State Sanitizers


// Sanitize sensitive data before sending to DevTools
const store = configureStore({
  reducer: rootReducer,
  devTools: {
    actionSanitizer: (action) => {
      // Hide sensitive data in actions
      if (action.type === 'auth/loginSuccess') {
        return {
          ...action,
          payload: {
            ...action.payload,
            token: '<>',
            password: undefined
          }
        };
      }
      return action;
    },
    
    stateSanitizer: (state) => {
      // Hide sensitive data in state
      return {
        ...state,
        auth: state.auth ? {
          ...state.auth,
          token: state.auth.token ? '<>' : null,
          user: state.auth.user ? {
            ...state.auth.user,
            password: undefined,
            creditCard: '<>'
          } : null
        } : state.auth
      };
    }
  }
});

// Custom serialization for non-serializable data
const store = configureStore({
  reducer: rootReducer,
  devTools: {
    serialize: {
      // Handle Map objects
      replacer: (key, value) => {
        if (value instanceof Map) {
          return {
            __serializedType__: 'Map',
            data: Array.from(value.entries())
          };
        }
        if (value instanceof Set) {
          return {
            __serializedType__: 'Set',
            data: Array.from(value)
          };
        }
        return value;
      },
      
      // Restore Map objects
      reviver: (key, value) => {
        if (value && value.__serializedType__ === 'Map') {
          return new Map(value.data);
        }
        if (value && value.__serializedType__ === 'Set') {
          return new Set(value.data);
        }
        return value;
      }
    }
  }
});
            

2. Performance Monitoring


// Monitor action execution time
const performanceMiddleware = store => next => action => {
  const start = performance.now();
  
  const result = next(action);
  
  const end = performance.now();
  const duration = end - start;
  
  // Send performance data to DevTools
  if (window.__REDUX_DEVTOOLS_EXTENSION__) {
    window.__REDUX_DEVTOOLS_EXTENSION__.send({
      type: '@@PERFORMANCE',
      payload: {
        action: action.type,
        duration: duration,
        timestamp: Date.now()
      }
    });
  }
  
  // Log slow actions
  if (duration > 16) { // Longer than one frame (60fps)
    console.warn(`Slow action ${action.type}: ${duration.toFixed(2)}ms`);
  }
  
  return result;
};

// Track state size
const stateSizeMiddleware = store => next => action => {
  const result = next(action);
  const state = store.getState();
  
  const stateSize = JSON.stringify(state).length;
  
  if (window.__REDUX_DEVTOOLS_EXTENSION__) {
    window.__REDUX_DEVTOOLS_EXTENSION__.send({
      type: '@@STATE_SIZE',
      payload: {
        size: stateSize,
        sizeInKB: (stateSize / 1024).toFixed(2),
        timestamp: Date.now()
      }
    });
  }
  
  return result;
};
            

3. Remote Debugging


// Remote Redux DevTools for debugging mobile apps
import { composeWithDevTools } from 'remote-redux-devtools';

const store = createStore(
  rootReducer,
  composeWithDevTools({
    name: 'Mobile App',
    realtime: true,
    hostname: 'localhost',
    port: 8000,
    secure: false,
    // Filters
    actionsBlacklist: ['EFFECT_RESOLVED'],
    actionsWhitelist: ['ADD_TODO', 'TOGGLE_TODO'],
    // Custom instance name
    instanceId: 'mobile-app-1'
  })(
    applyMiddleware(thunk)
  )
);

// Start the remote DevTools server
// npm install -g remotedev-server
// remotedev --hostname=localhost --port=8000
            

DevTools in Production

1. Conditional Loading


// Only load DevTools in development
const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== 'production',
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(
      process.env.NODE_ENV !== 'production' 
        ? [logger] 
        : []
    )
});

// Feature flag for DevTools
const devToolsEnabled = 
  process.env.NODE_ENV !== 'production' && 
  process.env.REACT_APP_ENABLE_DEVTOOLS === 'true';

const store = createStore(
  rootReducer,
  devToolsEnabled && window.__REDUX_DEVTOOLS_EXTENSION__ 
    ? window.__REDUX_DEVTOOLS_EXTENSION__()
    : undefined
);
            

2. Production Debugging


// Enable DevTools in production for specific users
const isAdminUser = () => {
  // Check if user has admin privileges
  const user = JSON.parse(localStorage.getItem('user') || '{}');
  return user.role === 'admin';
};

const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== 'production' || isAdminUser(),
  middleware: (getDefaultMiddleware) => {
    const middleware = getDefaultMiddleware();
    
    // Add logging for admin users in production
    if (process.env.NODE_ENV === 'production' && isAdminUser()) {
      middleware.push(createLogger({
        collapsed: true,
        duration: true,
        timestamp: true,
        level: 'info'
      }));
    }
    
    return middleware;
  }
});

// Production error tracking integration
const errorTrackingMiddleware = store => next => action => {
  try {
    return next(action);
  } catch (error) {
    // Send to error tracking service
    if (process.env.NODE_ENV === 'production') {
      Sentry.captureException(error, {
        extra: {
          action,
          state: store.getState()
        }
      });
    }
    throw error;
  }
};
            

DevTools Integration Examples

1. Debugging Async Actions


// Redux DevTools helps debug async action flow
const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async (_, { rejectWithValue }) => {
    try {
      const response = await api.getUsers();
      return response.data;
    } catch (err) {
      return rejectWithValue(err.response.data);
    }
  }
);

// DevTools will show:
// 1. users/fetchUsers/pending
// 2. API call (in Network tab)
// 3. users/fetchUsers/fulfilled (or rejected)
// 4. State updates after each action

// Custom async action logging
const asyncActionLogger = store => next => action => {
  if (action.type.endsWith('/pending')) {
    console.time(`Async Action: ${action.type}`);
  }
  
  const result = next(action);
  
  if (action.type.endsWith('/fulfilled') || action.type.endsWith('/rejected')) {
    console.timeEnd(`Async Action: ${action.type.replace(/\/(fulfilled|rejected)$/, '/pending')}`);
  }
  
  return result;
};
            

2. Testing with DevTools


// Export state from DevTools for test cases
const exportedState = {
  todos: [
    { id: 1, text: 'Test Todo', completed: false }
  ],
  filter: 'all'
};

// Import into test
import { createStore } from 'redux';
import rootReducer from './reducers';

describe('Todo App', () => {
  let store;
  
  beforeEach(() => {
    // Use exported state from DevTools
    store = createStore(rootReducer, exportedState);
  });
  
  test('completes todo', () => {
    store.dispatch({ 
      type: 'todos/todoCompleted', 
      payload: 1 
    });
    
    const state = store.getState();
    expect(state.todos[0].completed).toBe(true);
  });
});

// Generate tests from DevTools actions
const generateTestFromActions = (actions) => {
  return `
describe('Recorded user flow', () => {
  test('reproduces user actions', () => {
    const store = createStore(rootReducer);
    
    ${actions.map(action => `
    store.dispatch(${JSON.stringify(action)});
    expect(store.getState()).toMatchSnapshot();
    `).join('\n')}
  });
});
  `;
};
            

3. Performance Profiling


// Use DevTools with React Profiler
import { Profiler } from 'react';

function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update"
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions // the Set of interactions belonging to this update
) {
  // Send performance data to DevTools
  if (window.__REDUX_DEVTOOLS_EXTENSION__) {
    window.__REDUX_DEVTOOLS_EXTENSION__.send({
      type: '@@REACT_PERFORMANCE',
      payload: {
        component: id,
        phase,
        actualDuration,
        baseDuration,
        timestamp: commitTime
      }
    });
  }
}

function App() {
  return (
    
      
        
      
    
  );
}

// Track Redux selector performance
const selectorPerformanceMiddleware = store => next => action => {
  const start = performance.now();
  const result = next(action);
  const state = store.getState();
  
  // Measure selector computation time
  const selectorStart = performance.now();
  selectExpensiveData(state);
  const selectorDuration = performance.now() - selectorStart;
  
  if (selectorDuration > 5) { // Log slow selectors
    console.warn(`Slow selector execution: ${selectorDuration.toFixed(2)}ms`);
  }
  
  return result;
};
            

Common DevTools Workflows

1. Bug Investigation Workflow


// Step 1: Reproduce the bug
// Step 2: Open DevTools and observe action sequence
// Step 3: Use time travel to isolate the problematic action
// Step 4: Examine state diff to understand the issue
// Step 5: Fix the reducer or action creator

// Example debugging session
/*
1. User reports: "Todo items disappear after filtering"
2. DevTools shows actions:
   - todos/todoAdded
   - filter/filterChanged
   - todos/todosCleared (unexpected!)
3. Time travel to before todos/todosCleared
4. Examine action payload and state
5. Find issue: filter change incorrectly clearing todos
6. Fix reducer logic
*/

// Bug fix example
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    // ... other reducers
  },
  extraReducers: (builder) => {
    builder
      .addCase('filter/filterChanged', (state, action) => {
        // BUG: This was clearing todos
        // return [];
        
        // FIX: Don't modify todos on filter change
        return state;
      });
  }
});
            

2. Performance Investigation


// Using DevTools to identify performance issues

// 1. Enable trace in DevTools
const store = configureStore({
  reducer: rootReducer,
  devTools: {
    trace: true,
    traceLimit: 25
  }
});

// 2. Look for:
// - Frequent action dispatches
// - Large action payloads
// - Expensive state updates
// - Unnecessary re-renders

// 3. Use performance monitoring
const measurePerformance = (actionType) => {
  const measurements = [];
  
  return store => next => action => {
    if (action.type === actionType) {
      const start = performance.now();
      const result = next(action);
      const duration = performance.now() - start;
      
      measurements.push(duration);
      
      // Log statistics every 10 measurements
      if (measurements.length % 10 === 0) {
        const avg = measurements.reduce((a, b) => a + b) / measurements.length;
        const max = Math.max(...measurements);
        console.log(`${actionType} performance:`, {
          average: avg.toFixed(2),
          max: max.toFixed(2),
          samples: measurements.length
        });
      }
      
      return result;
    }
    return next(action);
  };
};

// Apply to specific actions
const store = createStore(
  rootReducer,
  applyMiddleware(
    measurePerformance('todos/todoAdded'),
    measurePerformance('filter/filterChanged')
  )
);
            

3. State Migration Workflow


// Using DevTools for state shape migrations

// 1. Export current state from DevTools
// 2. Transform state structure
// 3. Import transformed state
// 4. Test application functionality

// Migration script example
const migrateState = (oldState) => {
  // Old state shape
  // {
  //   todos: {
  //     items: [],
  //     filter: 'all'
  //   }
  // }
  
  // New state shape
  // {
  //   todos: {
  //     entities: {},
  //     ids: []
  //   },
  //   filter: 'all'
  // }
  
  const newState = {
    todos: {
      entities: {},
      ids: []
    },
    filter: oldState.todos.filter
  };
  
  // Transform todos array to normalized structure
  oldState.todos.items.forEach(todo => {
    newState.todos.entities[todo.id] = todo;
    newState.todos.ids.push(todo.id);
  });
  
  return newState;
};

// Test migration with DevTools
// 1. Export state from production app
// 2. Run migration script
// 3. Import migrated state into development
// 4. Verify functionality
            

Best Practices

Do's

  • Use DevTools during development for debugging
  • Sanitize sensitive data before it reaches DevTools
  • Use time travel to reproduce and fix bugs
  • Export states and actions for testing
  • Monitor performance with DevTools
  • Use action filtering for large applications

Don'ts

  • Don't enable DevTools in production by default
  • Don't expose sensitive user data in actions/state
  • Don't ignore DevTools performance warnings
  • Don't use DevTools as a replacement for proper logging
  • Don't forget to disable DevTools in production builds

Security Considerations


// Secure DevTools configuration
const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== 'production' ? {
    // Sanitize sensitive data
    actionSanitizer: (action) => {
      if (action.type.includes('auth')) {
        return { ...action, payload: '<>' };
      }
      return action;
    },
    stateSanitizer: (state) => ({
      ...state,
      auth: state.auth ? { 
        ...state.auth, 
        token: '<>' 
      } : undefined
    }),
    // Limit action history
    maxAge: 50,
    // Disable certain features
    features: {
      dispatch: false, // Prevent dispatching from DevTools
      import: false,   // Prevent importing states
    }
  } : false
});
                

Practice Exercise

Task: Set Up Advanced DevTools Configuration

Create a Redux store with advanced DevTools configuration that includes:

  • Conditional loading based on environment
  • Action and state sanitizers for sensitive data
  • Performance monitoring middleware
  • Custom action templates for testing
  • Remote debugging capability

// TODO: Create advanced DevTools setup
import { configureStore } from '@reduxjs/toolkit';
import { createLogger } from 'redux-logger';

// TODO: Create sanitizer functions
const actionSanitizer = (action) => {
  // Implement action sanitization
};

const stateSanitizer = (state) => {
  // Implement state sanitization
};

// TODO: Create performance monitoring middleware
const performanceMiddleware = store => next => action => {
  // Implement performance monitoring
};

// TODO: Create debug action templates
const debugActions = {
  testAuth: {
    type: 'auth/login',
    payload: { /* test data */ }
  },
  // Add more debug actions
};

// TODO: Configure store with DevTools
const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) => {
    // Configure middleware based on environment
  },
  devTools: {
    // Configure DevTools options
  }
});

// TODO: Add remote debugging support
const remoteDevTools = /* implement remote DevTools setup */;

// TODO: Create environment-specific configuration
const getStoreConfig = (environment) => {
  // Return different configurations for dev/staging/prod
};

export default store;
                

Additional Resources