Dynamic module loading for Redux with support for code splitting and lazy-loaded reducers and sagas
npm install redux-lazy-modulesA lightweight and powerful library for dynamically loading Redux modules at runtime. Perfect for code splitting, lazy loading, and modular Redux architecture in React applications.


- Features
- Why Redux Dynamic Modules?
- Installation
- Quick Start
- Complete Setup Guide
- Usage Patterns
- Advanced Examples
- API Reference
- TypeScript Support
- Example Application
- Best Practices
- Troubleshooting
- Contributing
- โ
Dynamic Module Loading - Load reducers and sagas on-demand at runtime
- ๐ Code Splitting - Split your Redux state into lazy-loaded chunks for better performance
- โก Performance Optimized - Only load what you need, when you need it
- ๐ง Full TypeScript Support - Complete type safety and IntelliSense
- ๐ช React Hooks - Modern hooks-based API (useDynamicModules)
- ๐จ HOC Pattern - Higher-Order Component pattern (withDynamicModules)
- ๐ฏ Redux Toolkit First - Built on modern Redux best practices
- ๐ฒ Tree Shakeable - Optimized bundle size with ES modules
- ๐ฅ Redux Saga Support - Full support for saga-based side effects
- ๐ฆ Zero Configuration - Works out of the box with sensible defaults
Traditional Redux applications load all reducers and state upfront, leading to:
- โ Large initial bundle sizes
- โ Slow application startup
- โ Loading code that users may never use
Redux Lazy Modules solves this by:
- โ
Loading Redux modules only when needed (route-based, feature-based)
- โ
Reducing initial bundle size by 50-70% in large applications
- โ
Improving Time to Interactive (TTI) metrics
- โ
Better code organization with self-contained modules
- โ
Enabling truly modular architecture
``bash`
npm install redux-lazy-modules
`bash`
yarn add redux-lazy-modules
`bash`
pnpm add redux-lazy-modules
This library requires the following peer dependencies:
`json`
{
"react": ">=18.0.0",
"react-redux": ">=8.0.0 || >=9.0.0",
"redux": ">=5.0.0",
"@reduxjs/toolkit": ">=2.0.0",
"redux-saga": ">=1.3.0" // Optional, only if using sagas
}
Install all required dependencies:
`bash`
npm install react react-dom react-redux redux @reduxjs/toolkit
If you plan to use Redux Saga:
`bash`
npm install redux-saga
`typescript
// src/store/index.ts
import { createDynamicStore } from "redux-lazy-modules";
import { Provider } from "react-redux";
// Create a store with dynamic module support
export const store = createDynamicStore({
devTools: true, // Enable Redux DevTools
});
// Export types for TypeScript
export type RootState = ReturnType
export type AppDispatch = typeof store.dispatch;
`
`typescript
// src/main.tsx or src/App.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
);
`
`typescript
// src/modules/counter/counterModule.ts
import { createSlice } from "@reduxjs/toolkit";
import { DynamicModule } from "redux-lazy-modules";
// Create a Redux Toolkit slice
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
// Export actions
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// Define the dynamic module
export const counterModule: DynamicModule = {
id: "counter", // Unique identifier
reducerMap: {
counter: counterSlice.reducer,
},
};
`
Option A: Using the Hook (Recommended)
`typescript
// src/pages/CounterPage.tsx
import { useDynamicModules } from 'redux-lazy-modules';
import { useDispatch, useSelector } from 'react-redux';
import { counterModule, increment, decrement } from '../modules/counter/counterModule';
function CounterPage() {
// Load the module dynamically
const { isLoaded } = useDynamicModules([counterModule]);
const dispatch = useDispatch();
const count = useSelector((state: any) => state.counter.value);
// Show loading state while module loads
if (!isLoaded) {
return
return (
export default CounterPage;
`
Option B: Using the HOC Pattern
`typescript
// src/pages/CounterPage.tsx
import { withDynamicModules } from 'redux-lazy-modules';
import { useDispatch, useSelector } from 'react-redux';
import { counterModule, increment, decrement } from '../modules/counter/counterModule';
function Counter() {
const dispatch = useDispatch();
const count = useSelector((state: any) => state.counter.value);
return (
// Wrap component with module loading
export default withDynamicModules([counterModule])(Counter);
`
#### Step 1: Project Setup
Create a new React + TypeScript project (or use your existing one):
`bash`
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
#### Step 2: Install Dependencies
`bash`
npm install redux-lazy-modules @reduxjs/toolkit react-redux redux
npm install redux-saga # Optional: if using sagas
#### Step 3: Configure Store
Create src/store/index.ts:
`typescript
import { createDynamicStore } from "redux-lazy-modules";
export const store = createDynamicStore({
devTools: process.env.NODE_ENV !== "production",
});
export type RootState = ReturnType
export type AppDispatch = typeof store.dispatch;
`
#### Step 4: Setup Provider
Update src/main.tsx:
`typescript
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
);
`
#### Step 5: Create Your First Module
Create src/modules/counter/counterModule.ts:
`typescript
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { DynamicModule } from "redux-lazy-modules";
// Define state interface
interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
// Create slice
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction
state.value += action.payload;
},
reset: (state) => {
state.value = 0;
},
},
});
export const { increment, decrement, incrementByAmount, reset } =
counterSlice.actions;
export const counterModule: DynamicModule = {
id: "counter",
reducerMap: {
counter: counterSlice.reducer,
},
};
// Selector
export const selectCounterValue = (state: { counter: CounterState }) =>
state.counter.value;
`
#### Step 6: Create Component
Create src/pages/CounterPage.tsx:
`typescript
import { useDynamicModules } from 'redux-lazy-modules';
import { useDispatch, useSelector } from 'react-redux';
import {
counterModule,
increment,
decrement,
incrementByAmount,
reset,
selectCounterValue
} from '../modules/counter/counterModule';
function CounterPage() {
const { isLoaded } = useDynamicModules([counterModule]);
const dispatch = useDispatch();
const count = useSelector(selectCounterValue);
if (!isLoaded) {
return
return (
export default CounterPage;
`
#### Step 7: Use in Your App
Update src/App.tsx:
`typescript
import CounterPage from './pages/CounterPage';
function App() {
return (
export default App;
`
That's it! You now have a working Redux Dynamic Modules setup. ๐
Best for: Functional components, modern React patterns
`typescript
import { useDynamicModules } from 'redux-lazy-modules';
function MyPage() {
const { isLoaded, loadedModules } = useDynamicModules([myModule]);
if (!isLoaded) return
return
}
`
Pros:
- โ
Clear loading state handling
- โ
Composable with other hooks
- โ
Easy to understand control flow
Best for: Class components, legacy code, when you don't need loading state in parent
`typescript
import { withDynamicModules } from "redux-lazy-modules";
function MyComponent() {
// Component code
}
export default withDynamicModules([myModule])(MyComponent);
// With custom loading component
export default withDynamicModules([myModule], CustomLoader)(MyComponent);
`
Pros:
- โ
Clean component code
- โ
Automatic loading state handling
- โ
Works with class components
Best for: Wrapping multiple children, conditional module loading
`typescript
import { WithDynamicModules } from 'redux-lazy-modules';
function MyPage() {
return (
);
}
`
Create an async module with side effects:
`typescript
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { call, put, takeEvery, delay } from "redux-saga/effects";
import { DynamicModule } from "redux-lazy-modules";
// State
interface UserState {
data: User | null;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
data: null,
loading: false,
error: null,
};
// Slice
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
fetchUserRequest: (state, action: PayloadAction
state.loading = true;
state.error = null;
},
fetchUserSuccess: (state, action: PayloadAction
state.data = action.payload;
state.loading = false;
},
fetchUserFailure: (state, action: PayloadAction
state.error = action.payload;
state.loading = false;
},
},
});
export const { fetchUserRequest, fetchUserSuccess, fetchUserFailure } =
userSlice.actions;
// Saga
function* fetchUserSaga(action: PayloadAction
try {
const response: Response = yield call(
fetch,
/api/users/${action.payload},
);
const user: User = yield response.json();
yield put(fetchUserSuccess(user));
} catch (error) {
yield put(fetchUserFailure((error as Error).message));
}
}
function* userSaga() {
yield takeEvery(fetchUserRequest.type, fetchUserSaga);
}
// Module
export const userModule: DynamicModule = {
id: "user",
reducerMap: {
user: userSlice.reducer,
},
sagas: [userSaga],
};
`
Combine with React.lazy for maximum optimization:
`typescript
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Lazy load pages
const HomePage = lazy(() => import('./pages/HomePage'));
const CounterPage = lazy(() => import('./pages/CounterPage'));
const TodosPage = lazy(() => import('./pages/TodosPage'));
const UserPage = lazy(() => import('./pages/UserPage'));
function App() {
return (
Each page loads its own Redux module:
`typescript
// pages/CounterPage.tsx
import { useDynamicModules } from "redux-lazy-modules";
import { counterModule } from "../modules/counter/counterModule";function CounterPage() {
const { isLoaded } = useDynamicModules([counterModule]);
// ... component code
}
`$3
Load multiple modules for complex features:
`typescript
import { useDynamicModules } from 'redux-lazy-modules';
import { userModule } from '../modules/user/userModule';
import { settingsModule } from '../modules/settings/settingsModule';
import { notificationsModule } from '../modules/notifications/notificationsModule';function DashboardPage() {
const { isLoaded } = useDynamicModules([
userModule,
settingsModule,
notificationsModule,
]);
if (!isLoaded) {
return ;
}
return (
);
}
`$3
Create a branded loading experience:
`typescript
function ModuleLoader() {
return (
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh'
}}>
Loading module...
export default withDynamicModules([myModule], ModuleLoader)(MyComponent);
`
Load modules based on user permissions or features:
`typescript
function AdminPage() {
const { user } = useAuth();
const modules = user.isAdmin
? [adminModule, analyticsModule, reportsModule]
: [basicModule];
const { isLoaded } = useDynamicModules(modules);
if (!isLoaded) return
return user.isAdmin ?
}
`
Advanced store setup with middleware and enhancers:
`typescript
import { createDynamicStore } from "redux-lazy-modules";
import { rootReducer } from "./rootReducer";
import logger from "redux-logger";
export const store = createDynamicStore({
// Initial reducers (always loaded)
initialReducers: {
app: rootReducer,
},
// Redux Toolkit store options
storeOptions: {
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: true,
serializableCheck: false,
}).concat(logger),
},
// Enable Redux DevTools
devTools: process.env.NODE_ENV !== "production",
});
`
Complete example with async operations:
`typescript
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { call, put, takeEvery } from "redux-saga/effects";
import { DynamicModule } from "redux-lazy-modules";
interface Todo {
id: string;
text: string;
completed: boolean;
}
interface TodosState {
items: Todo[];
loading: boolean;
error: string | null;
}
const initialState: TodosState = {
items: [],
loading: false,
error: null,
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
fetchTodosRequest: (state) => {
state.loading = true;
},
fetchTodosSuccess: (state, action: PayloadAction
state.items = action.payload;
state.loading = false;
},
addTodoRequest: (state, action: PayloadAction
state.loading = true;
},
addTodoSuccess: (state, action: PayloadAction
state.items.push(action.payload);
state.loading = false;
},
toggleTodoRequest: (state, action: PayloadAction
toggleTodoSuccess: (state, action: PayloadAction
const todo = state.items.find((t) => t.id === action.payload);
if (todo) todo.completed = !todo.completed;
},
deleteTodoRequest: (state, action: PayloadAction
deleteTodoSuccess: (state, action: PayloadAction
state.items = state.items.filter((t) => t.id !== action.payload);
},
},
});
export const {
fetchTodosRequest,
fetchTodosSuccess,
addTodoRequest,
addTodoSuccess,
toggleTodoRequest,
toggleTodoSuccess,
deleteTodoRequest,
deleteTodoSuccess,
} = todosSlice.actions;
// Sagas
function* fetchTodosSaga() {
try {
const response: Response = yield call(fetch, "/api/todos");
const todos: Todo[] = yield response.json();
yield put(fetchTodosSuccess(todos));
} catch (error) {
console.error("Failed to fetch todos:", error);
}
}
function* addTodoSaga(action: PayloadAction
try {
const response: Response = yield call(fetch, "/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: action.payload }),
});
const todo: Todo = yield response.json();
yield put(addTodoSuccess(todo));
} catch (error) {
console.error("Failed to add todo:", error);
}
}
function* toggleTodoSaga(action: PayloadAction
try {
yield call(fetch, /api/todos/${action.payload}/toggle, {
method: "PUT",
});
yield put(toggleTodoSuccess(action.payload));
} catch (error) {
console.error("Failed to toggle todo:", error);
}
}
function* deleteTodoSaga(action: PayloadAction
try {
yield call(fetch, /api/todos/${action.payload}, {
method: "DELETE",
});
yield put(deleteTodoSuccess(action.payload));
} catch (error) {
console.error("Failed to delete todo:", error);
}
}
function* todosSaga() {
yield takeEvery(fetchTodosRequest.type, fetchTodosSaga);
yield takeEvery(addTodoRequest.type, addTodoSaga);
yield takeEvery(toggleTodoRequest.type, toggleTodoSaga);
yield takeEvery(deleteTodoRequest.type, deleteTodoSaga);
}
export const todosModule: DynamicModule = {
id: "todos",
reducerMap: {
todos: todosSlice.reducer,
},
sagas: [todosSaga],
};
`
Using the todos module:
``typescript
import { useDynamicModules } from 'redux-lazy-modules';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
todosModule,
fetchTodosRequest,
addTodoRequest,
toggleTodoRequest,
deleteTodoRequest,
} from '../modules/todos/todosModule';
function TodosPage() {
const { isLoaded } = useDynamicModules([todosModule]);
const dispatch = useDispatch();
const { items, loading } = useSelector((state: any) => state.todos);
useEffect(() => {
if (isLoaded) {
dispatch(fetchTodosRequest());
}
}, [isLoaded, dispatch]);
const handleAddTodo = (text: string) => {
dispatch(addTodoRequest(text));
};
if (!isLoaded) return
return (
Loading...
}export default TodosPage;
Creates a Redux store with dynamic module loading capabilities.
#### Parameters
`typescript``
interface CreateDynamicStoreOptions {
initialReducers?: Record
storeOptions?: Partial
devTools?: boolean;
}
- initialReducers (optional): Initial reducers that are always loaded
- storeOptions (optional): Redux Toolkit configureStore optionsdevTools
- (optional): Enable Redux DevTools (default: process.env.NODE_ENV !== 'production')
#### Returns
A Redux store with attached reducerManager and sagaManager for dynamic module management.
#### Example
`typescript`
const store = createDynamicStore({
initialReducers: {
app: appReducer,
},
storeOptions: {
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
},
devTools: true,
});
---
React hook for dynamically loading Redux modules.
#### Parameters
- modules: DynamicModule[] - Array of modules to load
#### Returns
`typescript`
{
isLoaded: boolean; // True when all modules are loaded
loadedModules: Set
}
#### Example
`typescript
const { isLoaded, loadedModules } = useDynamicModules([counterModule, todosModule]);
if (!isLoaded) {
return
}
`
---
Higher-order component that wraps a component with dynamic module loading.
#### Parameters
- modules: DynamicModule[] - Modules to loadLoadingComponent
- (optional): React component to show while loading
#### Returns
HOC function that wraps your component
#### Example
`typescript
const EnhancedComponent = withDynamicModules([myModule], Loader)(MyComponent);
// Or as decorator
export default withDynamicModules([myModule])(MyComponent);
`
---
Component wrapper for loading dynamic modules.
#### Props
`typescript`
interface WithDynamicModulesProps {
modules: DynamicModule[];
loadingComponent?: React.ComponentType;
children: React.ReactNode;
}
#### Example
`typescript`
---
Interface for defining a dynamic Redux module.
`typescript
interface DynamicModule {
// Unique identifier for the module (required)
id: string;
// Map of reducer names to reducer functions
reducerMap?: Record
// Array of saga generator functions
sagas?: Array<() => Generator>;
}
`
#### Example
`typescript`
const myModule: DynamicModule = {
id: "myModule",
reducerMap: {
myFeature: myReducer,
},
sagas: [mySaga],
};
---
Type definition for a store with dynamic module managers.
`typescript`
interface StoreWithManagers extends Store {
reducerManager: ReducerManager;
sagaManager?: SagaManager;
}
---
Manages dynamic reducer registration and removal.
#### Methods
- add(key: string, reducer: Reducer) - Add a reducer
- remove(key: string) - Remove a reducer
- getReducerMap() - Get current reducer map
---
Manages dynamic saga registration and lifecycle.
#### Methods
- add(saga: Saga) - Add and run a saga
- remove(saga: Saga) - Cancel and remove a saga
---
Redux Dynamic Modules is written in TypeScript and provides comprehensive type definitions.
`typescript
import { createDynamicStore, StoreWithManagers } from "redux-lazy-modules";
export const store: StoreWithManagers = createDynamicStore();
// Export typed hooks
export type RootState = ReturnType
export type AppDispatch = typeof store.dispatch;
`
`typescript
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
// Typed versions of useDispatch and useSelector
export const useAppDispatch = () => useDispatch
export const useAppSelector: TypedUseSelectorHook
`
`typescript
import { DynamicModule } from "redux-lazy-modules";
import { Reducer } from "@reduxjs/toolkit";
interface MyState {
value: number;
}
const myReducer: Reducer
// reducer logic
return state;
};
export const myModule: DynamicModule = {
id: "myModule",
reducerMap: {
myFeature: myReducer,
},
};
`
`typescript
interface CounterState {
value: number;
}
interface RootState {
counter: CounterState;
}
export const selectCounterValue = (state: RootState): number =>
state.counter.value;
// Usage
const count = useAppSelector(selectCounterValue);
`
---
Always use unique, descriptive IDs for your modules:
`typescript
// โ
Good
export const userModule: DynamicModule = {
id: "user-profile",
reducerMap: { user: userReducer },
};
// โ Bad (generic ID)
export const userModule: DynamicModule = {
id: "user",
reducerMap: { user: userReducer },
};
`
Provide feedback while modules load:
`typescript
const { isLoaded } = useDynamicModules([myModule]);
if (!isLoaded) {
return
}
`
Keep related code together:
``
src/
modules/
user/
userModule.ts
userSlice.ts
userSagas.ts
userSelectors.ts
todos/
todosModule.ts
todosSlice.ts
todosSagas.ts
Maximize bundle size reduction:
`typescript
// Route-based code splitting
const UserPage = lazy(() => import("./pages/UserPage"));
// UserPage.tsx loads userModule dynamically
function UserPage() {
const { isLoaded } = useDynamicModules([userModule]);
// ...
}
`
Wrap dynamic components in error boundaries:
`typescript`
Define modules outside components to avoid recreating:
`typescript
// โ
Good - defined once
export const myModule: DynamicModule = { ... };
function MyComponent() {
const { isLoaded } = useDynamicModules([myModule]);
}
// โ Bad - recreated on every render
function MyComponent() {
const myModule = { id: 'my', reducerMap: { ... } };
const { isLoaded } = useDynamicModules([myModule]);
}
`
Create reusable, typed selector functions:
`typescript
// selectors.ts
export const selectUserData = (state: RootState) => state.user.data;
export const selectUserLoading = (state: RootState) => state.user.loading;
// component
const userData = useAppSelector(selectUserData);
`
Keep modules self-contained:
`typescript`
// myModule.ts
export { default as myModule } from "./module";
export * from "./actions";
export * from "./selectors";
export type { MyState } from "./types";
---
A full-featured example application is included in the example-app/ directory.
The example app demonstrates:
- โ
Counter module with HOC pattern (withDynamicModules)useDynamicModules
- โ
Todo list module with hooks pattern ()
- โ
Redux Saga integration for async API calls
- โ
Fake API layer for demonstrations
- โ
Code splitting and lazy loading
- โ
TypeScript with full type safety
- โ
Vite for fast development
- โ
Modern React patterns
`bash`
cd example-app
npm install
npm run dev
Open http://localhost:5173 to view it in the browser.
``
example-app/
โโโ src/
โ โโโ App.tsx # Main app component
โ โโโ CounterExample.tsx # Counter with HOC pattern
โ โโโ TodoListExample.tsx # Todos with hooks pattern
โ โโโ store/
โ โ โโโ index.ts # Store configuration
โ โ โโโ counterModule.ts # Counter module with sagas
โ โ โโโ todosModule.ts # Todos module with sagas
โ โโโ api/
โ โ โโโ counterApi.ts # Fake counter API
โ โ โโโ todosApi.ts # Fake todos API
โ โโโ components/
โ โโโ Loader.tsx # Loading component
---
Real-world impact on bundle sizes:
| Application Size | Without Dynamic Modules | With Dynamic Modules | Savings |
| ---------------- | ----------------------- | -------------------- | ------- |
| Small (< 50KB) | 45 KB | 40 KB | ~11% |
| Medium (< 500KB) | 425 KB | 180 KB | ~58% |
| Large (> 1MB) | 1.2 MB | 350 KB | ~71% |
- Initial Page Load: 40-60% faster
- Time to Interactive (TTI): 50-70% improvement
- First Contentful Paint (FCP): 30-40% faster
---
#### Issue: "Module not loading"
Solution: Check that the module ID is unique and the module is properly exported:
`typescript`
export const myModule: DynamicModule = {
id: "unique-id", // Must be unique!
reducerMap: { myReducer },
};
#### Issue: "State is undefined"
Solution: Make sure the module is loaded before accessing state:
`typescript
const { isLoaded } = useDynamicModules([myModule]);
if (!isLoaded) {
return
}
const data = useSelector(state => state.myData); // Safe now
`
#### Issue: "Saga not running"
Solution: Verify saga is correctly added to module:
`typescript`
export const myModule: DynamicModule = {
id: "my-module",
reducerMap: { myReducer },
sagas: [mySaga], // Must be an array of saga functions
};
#### Issue: "TypeScript errors with state"
Solution: Properly type your RootState:
`typescript
interface RootState {
counter?: CounterState; // Use optional for dynamic modules
todos?: TodosState;
}
const count = useSelector((state: RootState) => state.counter?.value ?? 0);
`
---
- โ
Chrome (latest)
- โ
Firefox (latest)
- โ
Safari (latest)
- โ
Edge (latest)
- โ
Modern mobile browsers
Minimum versions:
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
---
Contributions are welcome! Please read our Contributing Guide first.
`bashClone the repository
git clone https://github.com/amidulanjana/redux-lazy-modules.git
$3
`bash
npm test # Run tests
npm run test:coverage # Run with coverage
npm run typecheck # TypeScript type checking
npm run lint # Lint code
``---
MIT ยฉ Amila Dulanjana
---
- Redux Toolkit - The official Redux toolset
- Redux Saga - Side effect management for Redux
- React Redux - Official React bindings for Redux
- Reselect - Selector library for Redux
---
- ๐ Bug Reports: GitHub Issues
- ๐ฌ Discussions: GitHub Discussions
- ๐ Documentation: Full Documentation
- ๐ก Feature Requests: GitHub Issues
---
If you find this library helpful, please consider giving it a star on GitHub!
---
Made with โค๏ธ by the Redux community