✅ Explanation in Simple Terms
Think of your app like a big office building with 200+ rooms (screens).
Redux is the central storage room where shared files are kept.
To avoid chaos:
- Each floor = a large feature (Payments, Orders, Users)
- Each room = a screen inside the feature
- Each file cabinet = slice (collection of related state + reducers)
- The main building manager = root store
You NEVER mix files from different floors.
Each floor manages its OWN files.
The building manager (store) just connects all floors.
🎯 Goal of Good Redux Structure
- Easy to find state related to a feature
- Easy to debug
- Easy to scale (add more screens)
- Shared logic goes into shared slices
- Feature logic stays inside feature folders
⭐ Recommended Structure for 200+ Screens
/src
├── app/
│ ├── store.js
│ └── rootReducer.js
│
├── features/
│ ├── users/
│ │ ├── api/
│ │ │ └── usersApi.js (RTK Query optional)
│ │ ├── components/
│ │ │ └── UserTable.jsx
│ │ ├── pages/
│ │ │ └── UserListPage.jsx
│ │ ├── slices/
│ │ │ └── usersSlice.js
│ │ └── index.js
│ │
│ ├── payments/
│ │ ├── slices/
│ │ │ └── paymentSlice.js
│ │ ├── pages/
│ │ └── components/
│ │
│ ├── orders/
│ ├── dashboard/
│ └── ...
│
├── shared/
│ ├── slices/
│ │ ├── authSlice.js (login, roles)
│ │ ├── uiSlice.js (global loader, modals)
│ │ └── appConfigSlice.js (themes, global config)
│ ├── hooks/
│ └── utils/
│
├── components/ (truly generic UI)
└── index.js
🧠 Why This Structure Works for Enterprise Apps?
✔ Every feature is isolated
Each feature folder contains everything it needs:
- slice
- pages
- components
- API logic
Makes deleting or moving a feature VERY easy.
✔ Store stays clean
Root store only cares about:
{
users: usersReducer,
payments: paymentReducer,
orders: ordersReducer,
auth: authReducer,
ui: uiReducer
}
✔ Shared logic stays in /shared
Only truly global state sits here.
✔ Very easy to scale
When a new feature arrives:
features/newFeature/
slices/
pages/
components/
api/
No breaking changes.
🧩 Sample Enterprise Redux Store (Very Clean)
app/store.js
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';
export const store = configureStore({
reducer: rootReducer,
});
app/rootReducer.js
import { combineReducers } from '@reduxjs/toolkit';
import usersReducer from '../features/users/slices/usersSlice';
import paymentReducer from '../features/payments/slices/paymentSlice';
import orderReducer from '../features/orders/slices/orderSlice';
import authReducer from '../shared/slices/authSlice';
import uiReducer from '../shared/slices/uiSlice';
export default combineReducers({
users: usersReducer,
payments: paymentReducer,
orders: orderReducer,
auth: authReducer,
ui: uiReducer,
});
🍰 Example Users Slice (Feature-level Slice)
src/features/users/slices/usersSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
list: [],
loading: false,
};
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
setUsers(state, action) {
state.list = action.payload;
},
setLoading(state, action) {
state.loading = action.payload;
}
},
});
export const { setUsers, setLoading } = usersSlice.actions;
export default usersSlice.reducer;
📄 Example Feature Organization (Users)
/features/users
├── slices/
│ └── usersSlice.js
├── api/
│ └── usersApi.js (RTK Query or axios calls)
├── pages/
│ └── UserListPage.jsx
├── components/
│ └── UserTable.jsx
└── index.js
This makes the feature self-contained, modular, portable.
🧠 Where should Async API calls go?
Option A: Inside slice using createAsyncThunk
Good for large enterprise apps that need unified loading/error handling.
Option B: RTK Query
Best for auto caching, invalidation, etc.
Option C: Custom hook in feature folder
If business logic is complex.
📌 Rules for Enterprise Redux
✔ RULE 1: NEVER put everything in one big store
Each feature = 1 slice.
✔ RULE 2: Pages should never call the store directly
Use hooks:
const users = useSelector((state) => state.users.list);
const dispatch = useDispatch();
✔ RULE 3: Keep shared state small
Only:
- auth
- modals
- notifications
- app settings
- global loader
✔ RULE 4: Use createSlice everywhere
Its cleaner, faster, less boilerplate.
✔ RULE 5: Keep features isolated
Feature folders should be independent.
If a feature like Users grows big, you absolutely can have multiple slices inside the same feature.
Let me explain it in the simplest, cleanest way.
✅ Yes, you can have multiple slices inside src/features/users/slices/
Think of the feature like a mini-module.
Example:
src/features/users/
slices/
usersListSlice.js
userDetailsSlice.js
userPermissionsSlice.js
userFiltersSlice.js
Each slice manages its own independent part of the Users domain.
🧠 Why Use Multiple Slices?
Because enterprise apps often have:
- Users list screen
- User detail screen
- User permissions screen
- User role management
- Filters, sorting, pagination
- Local UI state (modal open/close)
Keeping all of this inside one huge usersSlice.js will turn it into a 4000-line monster.
Instead, you create small, focused slices.
🎯 Recommended Folder Structure for Multiple Slices
src/features/users/
├── slices/
│ ├── usersListSlice.js
│ ├── userDetailsSlice.js
│ ├── userPermissionsSlice.js
│ └── userFiltersSlice.js
│
├── api/
├── pages/
├── components/
└── index.js
🧩 Combine All User Slices Into One "users" Reducer
Inside your feature folder, create:
src/features/users/index.js
import { combineReducers } from "@reduxjs/toolkit";
import usersListReducer from "./slices/usersListSlice";
import userDetailsReducer from "./slices/userDetailsSlice";
import userPermissionsReducer from "./slices/userPermissionsSlice";
import userFiltersReducer from "./slices/userFiltersSlice";
export default combineReducers({
list: usersListReducer,
details: userDetailsReducer,
permissions: userPermissionsReducer,
filters: userFiltersReducer,
});
🏬 Now Your Root Reducer Uses Only One Key
Even though you have 4 slices, the store still sees them as ONE users domain:
rootReducer.js
import usersReducer from '../features/users';
export default combineReducers({
users: usersReducer,
payments: paymentReducer,
orders: orderReducer,
auth: authReducer,
});
📦 Final Redux State Looks Like This
{
users: {
list: { ... },
details: { ... },
permissions: { ... },
filters: { ... }
},
payments: { ... },
orders: { ... },
auth: { ... }
}
Clean.
Organized.
Enterprise ready.
🎁 Example Slice (usersListSlice.js)
import { createSlice } from "@reduxjs/toolkit";
const usersListSlice = createSlice({
name: "usersList",
initialState: {
data: [],
loading: false,
error: null
},
reducers: {
setUsers(state, action) {
state.data = action.payload;
},
setLoading(state, action) {
state.loading = action.payload;
}
}
});
export const { setUsers, setLoading } = usersListSlice.actions;
export default usersListSlice.reducer;
Each slice stays small and readable.
🎯 When Should You Split Slices?
Split when a slice grows beyond 300–400 lines or handles different screens.
Examples:
| Slice | Purpose |
|---|---|
usersListSlice |
list table, pagination, loading |
userFiltersSlice |
search, filters, sorting |
userDetailsSlice |
profile page, details API |
userPermissionsSlice |
roles, permissions matrix |
This is perfect for a 200+ screen enterprise app.
🚀 Summary (Very Simple)
✔ Yes, put multiple slices inside the same feature
✔ Combine them inside features/users/index.js
✔ Root reducer still uses only users:
✔ Clean and scalable
🏆 TL;DR (Easiest Summary)
Redux for 200+ screens should be structured like this:
- app/ = store + root reducer
- features/ = one folder per big feature
- shared/ = global slices like auth/ui
- pages/components/api inside each feature
- each feature has its own slice (usersSlice, ordersSlice, etc.)
This makes the application:
- Easy to scale
- Easy to debug
- Easy to onboard new developers
- Modular and maintainable
Top comments (0)