Scoped Redux Stores per Component Instance (Truly Isolated State in React)
Redux is usually a singleton, meaning one giant store for the whole app. But what if you want fully isolated Redux stores — one per component instance? Maybe you're rendering independent widgets, dynamic tabs, or embedding apps inside apps. Here's how to create **scoped Redux stores** per component, without polluting the global app state. Why Scoped Redux Stores? Common use cases: Multiple instances of the same feature (e.g., chat rooms, dashboards) Micro-frontend components embedded independently Dynamic UIs where each "widget" needs clean, isolated state Step 1: Create a Store Factory Instead of a singleton store, make a factory that creates a store per use: // src/store/createScopedStore.js import { configureStore } from '@reduxjs/toolkit'; import { reducer } from './slices/scopedSlice'; export function createScopedStore() { return configureStore({ reducer, }); } Step 2: Create a Local Provider You can't use the global because it only works once. So, create a component that injects its own store dynamically: // src/components/ScopedStoreProvider.js import { Provider } from 'react-redux'; import { createScopedStore } from '../store/createScopedStore'; export function ScopedStoreProvider({ children }) { const store = createScopedStore(); return {children}; } Step 3: Build a Local Slice This slice will live only inside the scoped store: // src/store/slices/scopedSlice.js import { createSlice } from '@reduxjs/toolkit'; const scopedSlice = createSlice({ name: 'scoped', initialState: { count: 0 }, reducers: { increment(state) { state.count++; }, decrement(state) { state.count--; }, }, }); export const { increment, decrement } = scopedSlice.actions; export const reducer = scopedSlice.reducer; Step 4: Use It in a Component Each instance will have completely independent Redux state! // src/components/CounterWidget.js import { ScopedStoreProvider } from './ScopedStoreProvider'; import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement } from '../store/slices/scopedSlice'; export function CounterWidget() { return ( ); } function InnerCounter() { const count = useSelector((state) => state.count); const dispatch = useDispatch(); return ( Scoped Counter: {count} dispatch(increment())}>+ dispatch(decrement())}>- ); } How It Works Every ScopedStoreProvider creates a brand new Redux store. The store is private to its subtree — no shared or conflicting state. Perfect for highly modular apps where components must stay isolated. Pros and Cons ✅ Pros Absolute isolation between instances No risk of state collisions Useful for embedding Redux components into third-party UIs ⚠️ Cons More memory usage (each store is a full Redux store) DevTools need extra configuration to track multiple stores Harder to share cross-instance state without lifting up
Redux is usually a singleton, meaning one giant store for the whole app. But what if you want fully isolated Redux stores — one per component instance? Maybe you're rendering independent widgets, dynamic tabs, or embedding apps inside apps. Here's how to create **scoped Redux stores** per component, without polluting the global app state.
Why Scoped Redux Stores?
Common use cases:
- Multiple instances of the same feature (e.g., chat rooms, dashboards)
- Micro-frontend components embedded independently
- Dynamic UIs where each "widget" needs clean, isolated state
Step 1: Create a Store Factory
Instead of a singleton store, make a factory that creates a store per use:
// src/store/createScopedStore.js
import { configureStore } from '@reduxjs/toolkit';
import { reducer } from './slices/scopedSlice';
export function createScopedStore() {
return configureStore({
reducer,
});
}
Step 2: Create a Local Provider
You can't use the global
because it only works once. So, create a component that injects its own store dynamically:
// src/components/ScopedStoreProvider.js
import { Provider } from 'react-redux';
import { createScopedStore } from '../store/createScopedStore';
export function ScopedStoreProvider({ children }) {
const store = createScopedStore();
return {children} ;
}
Step 3: Build a Local Slice
This slice will live only inside the scoped store:
// src/store/slices/scopedSlice.js
import { createSlice } from '@reduxjs/toolkit';
const scopedSlice = createSlice({
name: 'scoped',
initialState: { count: 0 },
reducers: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
},
},
});
export const { increment, decrement } = scopedSlice.actions;
export const reducer = scopedSlice.reducer;
Step 4: Use It in a Component
Each instance will have completely independent Redux state!
// src/components/CounterWidget.js
import { ScopedStoreProvider } from './ScopedStoreProvider';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../store/slices/scopedSlice';
export function CounterWidget() {
return (
);
}
function InnerCounter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
Scoped Counter: {count}
);
}
How It Works
- Every
ScopedStoreProvider
creates a brand new Redux store. - The store is private to its subtree — no shared or conflicting state.
- Perfect for highly modular apps where components must stay isolated.
Pros and Cons
✅ Pros
- Absolute isolation between instances
- No risk of state collisions
- Useful for embedding Redux components into third-party UIs
⚠️ Cons
- More memory usage (each store is a full Redux store)
- DevTools need extra configuration to track multiple stores
- Harder to share cross-instance state without lifting up