Redux is a predictable state container for JavaScript applications. It helps manage the state of your application in a more structured way. This chapter will guide you through the basics of Redux, advanced concepts, and practical examples to give you a comprehensive understanding. By the end of this chapter, you will have a solid grasp of Redux and how to integrate it with your React applications.
Redux is a library for managing and centralizing application state. It is often used with React but can be used with any JavaScript framework or library. Redux provides a predictable state container, making state management more manageable and debugging easier.
There are several reasons to use Redux in your application:
Redux revolves around three core concepts:
// Action
const increment = () => {
return {
type: 'INCREMENT'
};
};
// Reducer
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
// Store
const store = Redux.createStore(counter);
// Dispatch
store.dispatch(increment());
console.log(store.getState()); // Output: 1
To set up Redux in a React application, follow these steps:
npm install redux react-redux
store.js
file:
// src/store.js
import { createStore } from 'redux';
// Initial state
const initialState = {
count: 0
};
// Reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
// Create store
const store = createStore(reducer);
export default store;
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
ReactDOM.render(
,
document.getElementById('root')
);
App.js
file:
// src/App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
function App() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
Count: {count}
);
}
export default App;
Provider
component from react-redux
to pass the store to the React application.useSelector
to access the state and useDispatch
to dispatch actions.In this example, we’ll create a simple Counter application using Redux. The application will display a counter and provide buttons to increment and decrement its value.
Step-by-Step Implementation
First, let’s set up the Redux store, actions, and reducers.
store.js
:
// store.js
import { createStore } from 'redux';
// Initial state
const initialState = {
count: 0
};
// Reducer
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
// Create store
const store = createStore(counterReducer);
export default store;
Now, let’s connect the Redux store to our React application using react-redux
.
index.js
:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
ReactDOM.render(
,
document.getElementById('root')
);
Create the main application component App.js
and connect it to Redux to display the counter and handle actions.
App.js
:
// App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
function App() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
const decrement = () => {
dispatch({ type: 'DECREMENT' });
};
return (
Counter: {count}
);
}
export default App;
store.js
: Defines the initial state and a reducer function (counterReducer
) that handles state updates based on dispatched actions (INCREMENT
and DECREMENT
). Creates the Redux store using createStore
.
index.js
: Wraps the main App
component with Provider
from react-redux
, passing in the Redux store as a prop to provide access to Redux state throughout the component tree.
App.js
: Uses useSelector
to access the count
state from the Redux store and useDispatch
to dispatch INCREMENT
and DECREMENT
actions. Renders the counter value and buttons to increment and decrement the counter.
When you run the application, you’ll see a counter initialized to 0 with buttons to increment and decrement its value. Clicking the buttons dispatches actions to update the counter, and the UI updates accordingly.
In this example, we’ll create a Todo application using Redux. This application will allow users to add and remove todo items.
Step-by-Step Implementation
store.js
:
// store.js
import { createStore } from 'redux';
// Initial state
const initialState = {
todos: []
};
// Reducer
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TODO':
return { ...state, todos: [...state.todos, action.payload] };
case 'REMOVE_TODO':
return { ...state, todos: state.todos.filter((todo, index) => index !== action.payload) };
default:
return state;
}
};
// Create store
const store = createStore(todoReducer);
export default store;
index.js
:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
ReactDOM.render(
,
document.getElementById('root')
);
Create the main application component App.js
and connect it to Redux to manage todo items.
App.js
:
// App.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
function App() {
const [todo, setTodo] = useState('');
const todos = useSelector(state => state.todos);
const dispatch = useDispatch();
const addTodo = () => {
if (todo.trim() !== '') {
dispatch({ type: 'ADD_TODO', payload: todo });
setTodo('');
}
};
const removeTodo = (index) => {
dispatch({ type: 'REMOVE_TODO', payload: index });
};
return (
Todo List
setTodo(e.target.value)}
placeholder="Enter a todo..."
/>
{todos.map((todo, index) => (
-
{todo}
))}
);
}
export default App;
store.js
: Defines the initial state and a reducer function (todoReducer
) that handles state updates for todo items based on dispatched actions (ADD_TODO
and REMOVE_TODO
). Creates the Redux store using createStore
.
index.js
: Wraps the main App
component with Provider
from react-redux
, passing in the Redux store as a prop to provide access to Redux state throughout the component tree.
App.js
: Uses local state (useState
) to manage input for adding new todo items. Uses useSelector
to access the todos
state from the Redux store and useDispatch
to dispatch ADD_TODO
and REMOVE_TODO
actions. Renders the todo list with items and buttons to add new todos and remove existing ones.
When you run the application, you’ll see a todo list with an input field and a button to add new todo items. Each todo item can be removed by clicking on the corresponding “Remove” button.
Middleware in Redux provides a third-party extension point between dispatching an action and the moment it reaches the reducer. Middleware can intercept actions, perform asynchronous tasks, log actions, modify actions, or dispatch additional actions.
Redux Thunk is a popular middleware for handling asynchronous logic in Redux. It allows action creators to return functions instead of plain objects, enabling delayed actions or side effects.
store.js
:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers'; // assuming you have a rootReducer
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
export default store;
// actions.js
import axios from 'axios';
export const fetchPosts = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_POSTS_REQUEST' });
try {
const response = await axios.get('/api/posts');
dispatch({ type: 'FETCH_POSTS_SUCCESS', payload: response.data });
} catch (error) {
dispatch({ type: 'FETCH_POSTS_FAILURE', payload: error.message });
}
};
};
In this example:
fetchPosts()
action creator, allowing it to dispatch actions asynchronously after fetching data from an API.Redux itself is synchronous by nature, but with middleware like Redux Thunk, Redux Saga, or Redux Observable, you can handle asynchronous operations like fetching data or performing API calls.
// actions.js
import axios from 'axios';
export const fetchData = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_DATA_REQUEST' });
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: response.data });
} catch (error) {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: error.message });
}
};
};
In this example:
fetchData()
asynchronously fetches data from an API using Axios and dispatches appropriate actions based on success or failure.Redux DevTools is a powerful tool for debugging Redux applications. It allows you to inspect every action and state change, rewind to previous states, and track performance.
// store.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './reducers'; // assuming you have a rootReducer
const store = createStore(
rootReducer,
composeWithDevTools(
applyMiddleware(...middlewares)
)
);
export default store;
index.js
:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
ReactDOM.render(
,
document.getElementById('root')
);
Redux is a powerful tool for managing state in JavaScript applications. By understanding its core concepts, setting it up in a React application, and leveraging advanced features, you can build scalable and maintainable applications. Remember to follow best practices and use Redux only when necessary to avoid overcomplicating your application.Happy coding !❤️