Redux¶
What is Redux?¶
Redux is a state management library for JavaScript applications, often used with React but can also be used with Angular, Vue, or plain JS. It helps manage the state of an application in a predictable way using a single source of truth (the store).
Why Use Redux?¶
- Centralized State Management: All application state is stored in one place (the store).
- Predictable State Changes: State can only be changed through specific functions (reducers) triggered by actions.
- Easier Debugging: Redux provides tools to track every state change (Redux DevTools).
- Consistency Across Components: Different components can easily access and update the same state.
- Improved Maintainability: Clear flow of data and separation of concerns.
Redux Core Concepts¶
1. Store¶
- The store is a JavaScript object that holds the entire state tree of your application.
- There is only one store in a Redux application.
import { createStore } from "redux";
const store = createStore(reducer);
2. Actions¶
- Actions are plain JavaScript objects that describe what happened.
- Each action has a type (a string constant) and optionally some payload (data).
const incrementAction = {
type: "INCREMENT",
};
const addTodoAction = {
type: "ADD_TODO",
payload: "Learn Redux",
};
3. Reducers¶
- Reducers are pure functions that specify how the state changes in response to actions.
- They take the current state and an action as arguments and return a new state.
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
}
4. Dispatch¶
- The
dispatch()function sends an action to the Redux store. - When an action is dispatched, the store runs the reducer to update the state.
store.dispatch({ type: "INCREMENT" });
5. Subscribe¶
- The
subscribe()method allows you to listen for state changes in the store.
store.subscribe(() => {
console.log(store.getState());
});
Redux Data Flow (Unidirectional)¶
- Dispatch an Action – Trigger an event that describes what happened.
- Reducer – Receives the action and current state, returns a new state.
- Store – Updates the state based on the reducer’s output.
- UI – Re-renders based on the updated state.
Flow Diagram:
UI → Action → Reducer → Store → UI
Example: Simple Counter¶
import { createStore } from "redux";
// Reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
}
// Create store
const store = createStore(counterReducer);
// Subscribe
store.subscribe(() => console.log(store.getState()));
// Dispatch actions
store.dispatch({ type: "INCREMENT" }); // {count: 1}
store.dispatch({ type: "INCREMENT" }); // {count: 2}
store.dispatch({ type: "DECREMENT" }); // {count: 1}
Using Redux with React¶
Install:¶
npm install redux react-redux
Provider Setup¶
Wrap your app with the Redux Provider to give components access to the store.
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import App from "./App";
import counterReducer from "./counterReducer";
const store = createStore(counterReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Accessing State and Dispatch in Components¶
useSelector – to get state¶
useDispatch – to send actions¶
import React from "react";
import { useSelector, useDispatch } from "react-redux";
function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
<div>
<h1>{count}</h1>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
</div>
);
}
export default Counter;
Redux Toolkit (RTK)¶
Redux Toolkit simplifies Redux with less boilerplate.
Install:¶
npm install @reduxjs/toolkit react-redux
Create a Slice¶
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { count: 0 },
reducers: {
increment: (state) => {
state.count += 1;
},
decrement: (state) => {
state.count -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
const store = configureStore({ reducer: counterSlice.reducer });
export default store;
Use in Component¶
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement } from "./store";
function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
<>
<h1>{count}</h1>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</>
);
}
Middleware in Redux¶
Middleware allows handling of side effects (like async API calls).
Common Middleware:¶
- redux-thunk: For asynchronous actions.
- redux-saga: For complex side effects and background tasks.
Example (redux-thunk):¶
npm install redux-thunk
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
const fetchData = () => {
return async (dispatch) => {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
dispatch({ type: "DATA_LOADED", payload: data });
};
};
Redux DevTools¶
- A browser extension that lets you inspect actions, state, and changes.
- Install it and enable it in your store setup:
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
Advantages of Redux¶
- Predictable state changes
- Easy debugging and testing
- Works well with any UI layer (not limited to React)
- Centralized and consistent data handling
Disadvantages of Redux¶
- Boilerplate code (reduced by Redux Toolkit)
- Complex setup for small projects
- Learning curve for beginners
When to Use Redux¶
Use Redux when:
- You have complex state logic shared across many components.
- Multiple components need access to the same data.
- You want predictable and trackable state changes.
Avoid Redux when:
- The app is small and simple.
- State can be handled easily using React’s
useStateoruseContext.