How `replaceReducer` Enables Code Splitting and Hot Reloading in Redux
The replaceReducer method swaps the root reducer at runtime, allowing Redux to dynamically load new state slices for code splitting and instantly update reducer logic during development for hot module replacement without losing application state.
replaceReducer is a core API method in the reduxjs/redux repository that removes the limitation of having a single, static root reducer. By enabling runtime reducer replacement, it powers two essential modern development techniques: lazy-loading reducers for code-split applications and hot reloading for rapid iteration.
Understanding replaceReducer
In src/createStore.js, the Redux store factory exposes replaceReducer as a method that accepts a new reducer function and immediately swaps the internal currentReducer reference. After the swap, it dispatches a dummy action (@@redux/REPLACE) to ensure any newly added state slices initialize with their default values. This mechanism preserves the existing state tree while allowing the logic that processes actions to change completely.
Code Splitting with replaceReducer
The Challenge of Dynamic Reducers
Large applications often use route-based or feature-based code splitting to reduce initial bundle size. However, Redux traditionally requires all reducers to be known at store creation time via combineReducers. When a lazy-loaded chunk arrives with its own state slice, the store must incorporate the new reducer without recreation.
Implementing Lazy-Loaded Reducers
The Universal example in the Redux repository demonstrates the standard pattern. In examples/universal/common/store/configureStore.js, the store exposes an injectReducer helper that maintains a registry of async reducers:
// examples/universal/common/store/configureStore.js
import { createStore, combineReducers } from 'redux'
import staticReducers from './staticReducers'
export default function configureStore(initialState) {
const store = createStore(createRootReducer(), initialState)
// Registry for dynamically added reducers
store.asyncReducers = {}
// Helper to inject new reducers
store.injectReducer = (key, asyncReducer) => {
store.asyncReducers[key] = asyncReducer
store.replaceReducer(createRootReducer(store.asyncReducers))
}
return store
}
function createRootReducer(asyncReducers = {}) {
return combineReducers({
...staticReducers,
...asyncReducers,
})
}
When a code-split chunk loads, it calls store.injectReducer('feature', featureReducer), triggering replaceReducer to merge the new slice into the root reducer without losing existing state.
Hot Module Replacement with replaceReducer
Webpack HMR Integration
During development, Webpack’s Hot Module Replacement (HMR) allows reducer code to be updated without a full page refresh. The Real-World example in examples/real-world/src/store/configureStore.dev.js implements this pattern:
// examples/real-world/src/store/configureStore.dev.js
import { createStore } from 'redux'
import rootReducer from '../reducers'
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState)
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
return store
}
When the reducer files change, Webpack triggers the accept callback, which imports the updated reducer module and calls replaceReducer. The store immediately begins using the new logic while preserving all existing state and subscribed listeners.
Internal Mechanics and State Initialization
According to the Redux source in src/createStore.js, replaceReducer performs two critical operations:
- Reference Swap: It updates the internal
currentReducervariable to the new function passed as an argument. - Re-initialization Dispatch: It dispatches an action with type
@@redux/REPLACE(or similar internal type) to force the new reducer to populate any default state for newly added slices.
This ensures that when code splitting adds a new state slice, the UI can immediately access the new slice's initial state without manual intervention.
Summary
replaceReducerswaps the root reducer function at runtime without recreating the store or losing state.- Code splitting relies on
replaceReducerto inject lazy-loaded reducers into the existing store, typically via a helper likeinjectReducerthat rebuilds the combined reducer and callsreplaceReducer. - Hot reloading uses
replaceReducerin Webpack HMR accept handlers to swap updated reducer code instantly during development. - The method dispatches an internal initialization action to ensure new state slices receive their default values.
Frequently Asked Questions
What happens to existing state when replaceReducer is called?
Existing state is preserved. replaceReducer only updates the function used to process actions. However, if the new reducer handles different state keys than the previous one, any state for keys no longer handled will be removed from the state tree, while new keys will be initialized with their default values.
Can replaceReducer be used with Redux Toolkit?
Yes. While Redux Toolkit's configureStore simplifies store setup, it returns a standard Redux store that includes replaceReducer. You can access it directly or use the injectReducer pattern with RTK's combineSlices or traditional combineReducers to enable code splitting in applications using Redux Toolkit.
How does replaceReducer differ from creating a new store?
Creating a new store with createStore initializes a fresh state tree and requires re-subscribing all listeners, which breaks the application connection. replaceReducer maintains the existing store instance, state reference, and subscriber registry, enabling seamless updates without component unmounting or state loss.
Is replaceReducer safe for production applications?
Yes, replaceReducer is a stable, public API used safely in production for code-splitting scenarios. However, it should be used carefully to ensure the new reducer maintains compatibility with existing state shapes. For hot reloading, it is typically disabled in production builds since HMR is a development-only feature.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →