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
🚀 Alternatives
- Zustand, Recoil: Designed for local/global blending naturally
- Context + useReducer: Lightweight and scoped, but loses Redux tooling
Summary
If you need true component-level isolation with Redux tooling, scoped stores are your hidden weapon. Use it carefully — it’s a powerful pattern but not always needed in traditional apps.
For a much more extensive guide on getting the most out of React portals, check out my full 24-page PDF file on Gumroad. It's available for just $10:
Using React Portals Like a Pro.
If you found this useful, you can support me here: buymeacoffee.com/hexshift