React Redux is a popular state management library that enables developers to manage and centralize the state of a React application in a predictable and efficient way.
By separating the application state from components, Redux allows React applications to scale more easily and maintain a consistent state across different components.
Key Concepts of Redux
- Single Source of Truth: The entire state of the application is stored in a single JavaScript object called the “store,” making it the central location for managing data.
- State is Read-Only: Redux enforces a strict unidirectional data flow. The only way to modify the state is by dispatching actions.
- Pure Reducers: Reducers are pure functions that take the current state and an action as arguments and return a new state based on the action type.
Why Use Redux with React?
React’s internal state management is sufficient for simple applications, but as the app grows, managing state across many components can become complex.
Redux provides a robust solution by centralizing the state, allowing developers to manage complex application states with ease. It helps:
- Ensure predictable state management.
- Enable time-travel debugging (by tracking all state changes).
- Improve component reusability and testability.
Setting Up React Redux
To use Redux with React, install both redux and react-redux packages.
npm install redux react-redux
Core Redux Components
- Store: The centralized location where the application’s state lives.
- Actions: Objects that represent an intention to change the state. They contain a type and optionally a payload.
- Reducers: Pure functions that specify how the state changes in response to actions.
- Dispatch: The mechanism that sends actions to the store.
Creating a Basic Redux Store
A Redux store is created using the createStore function, where the root reducer is passed as an argument.
import { createStore } from 'redux';
// Reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
}
// Store
const store = createStore(counterReducer);
export default store;
In this example:
- The counterReducer handles two action types, INCREMENT and DECREMENT, and modifies the count property of the state accordingly.
- The store is created with createStore, taking counterReducer as an argument to manage state changes.
Connecting Redux with React
To connect Redux to a React application, use the Provider component from react-redux, which makes the Redux store accessible to all components within the app.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Explanation:
- Provider wraps the main App component, enabling access to the Redux store across all child components.
Dispatching Actions
Actions in Redux are plain JavaScript objects with a type property and an optional payload.
const incrementAction = { type: 'INCREMENT' };
const decrementAction = { type: 'DECREMENT' };
store.dispatch(incrementAction);
In this example:
- Actions incrementAction and decrementAction specify the type of change to be applied.
- store.dispatch is used to send these actions to the store, triggering the reducer to process the action and update the state.
Using react-redux Hooks
react-redux provides hooks such as useSelector and useDispatch to access the state and dispatch actions from within React components.
- useSelector: Used to select state from the store.
- useDispatch: Used to dispatch actions to the store.
Example: Increment and Decrement Counter with Redux
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
const increment = () => dispatch({ type: 'INCREMENT' });
const decrement = () => dispatch({ type: 'DECREMENT' });
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
Explanation:
- useSelector retrieves the count from the Redux store.
- useDispatch returns a dispatch function that is used to send INCREMENT and DECREMENT actions to the store.
- When buttons are clicked, the corresponding action is dispatched, updating the count value in the store.
Combining Multiple Reducers
As the application grows, it’s common to separate logic into multiple reducers and combine them into a root reducer using combineReducers.
import { combineReducers, createStore } from 'redux';
function counterReducer(state = { count: 0 }, action) {
// Handle counter actions
}
function userReducer(state = { name: '' }, action) {
// Handle user actions
}
const rootReducer = combineReducers({
counter: counterReducer,
user: userReducer
});
const store = createStore(rootReducer);
In this example:
- combineReducers is used to combine counterReducer and userReducer.
- rootReducer is then passed to createStore, enabling a modular approach to state management.
Middleware in Redux
Middleware allows for customization of the dispatch process, enabling asynchronous actions, logging, and more. A common middleware is redux-thunk, which allows you to dispatch functions (for asynchronous actions) instead of plain action objects.
Install redux-thunk:
npm install redux-thunk
Configure Store with Middleware:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Example of an Async Action with Thunk:
const fetchData = () => {
return async (dispatch) => {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'SET_DATA', payload: data });
};
};
Explanation:
- fetchData is an asynchronous action creator that fetches data from an API and then dispatches an action with the fetched data as payload.
Redux DevTools
Redux DevTools is a browser extension that provides a time-travel debugging experience, allowing developers to inspect and debug state changes in the store.
Installing Redux DevTools:
npm install redux-devtools-extension
Enabling Redux DevTools in Store Configuration:
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(rootReducer, composeWithDevTools());
Summary of React Redux
Concept | Description |
---|---|
Store | The centralized place for the application state |
Actions | Plain JavaScript objects that describe what should change in the state |
Reducers | Pure functions that specify how the state changes in response to actions |
Dispatch | Function used to send actions to the store |
useSelector Hook | Allows components to read state from the store |
useDispatch Hook | Allows components to dispatch actions to the store |
Middleware | Functions that allow customization of dispatching actions, used for async actions, logging, etc. |
Redux DevTools | Tool to inspect state changes and time-travel debug actions |