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

Apr 27, 2025 - 17:34
 0
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}

); }

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