INSTALLATION ANS SETUP
To use Redux in a JavaScript application, you need to set up the store, define actions and reducers, dispatch actions to modify the state, and connect components to the store to access the state and listen for updates.
Redux maintainers do recommend using Redux Toolkit instead of the Redux core package for new Redux code.
Implementing Redux
To get started with Redux, follow these steps:
Create a React App: Begin by creating a React application using your preferred tool.
Install Redux Toolkit, React Redux: Install the Redux Toolkit, which simplifies Redux setup.
npm i @reduxjs/toolkit react-redux
Configure the Store: Use
configureStore
to set up the global store object. This store will register any reducers defined elsewhere in your code.import { configureStore } from '@reduxjs/toolkit' export const store = configureStore({ reducer: {}, })
Provider Component: Wrap your application with the
Provider
component. This component makes the store's data accessible to the entire component tree.import React from 'react' import ReactDOM from 'react-dom' import './index.css' import App from './App' import { store } from './app/store' import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
Create Slices: Create "slices" to represent data in the store related to a feature/component of an application. Slices help in modularizing your Redux state and reducers, making the codebase more maintainable. A slice includes a unique name, an initial state, and a collection of reducer functions.
// tasksSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { tasks: [], isLoading: false, error: null, }; const tasksSlice = createSlice({ name: 'myTasks', // For dev tools initialState, reducers: { addTask: (state, action) => { state.tasks.push(action.payload); }, removeTask: (state, action) => { state.tasks = state.tasks.filter(task => task.id !== action.payload); }, toggleTask: (state, action) => { state.tasks = state.tasks.map(task => task.id === action.payload ? { ...task, completed: !task.completed } : task ); }, setLoading: (state, action) => { state.isLoading = action.payload; }, setError: (state, action) => { state.error = action.payload; }, }, }); export const { addTask, removeTask, toggleTask, setLoading, setError } = tasksSlice.actions; export default tasksSlice.reducer;
// store.js import { configureStore } from '@reduxjs/toolkit'; import tasksReducer from '../slices/tasksSlice'; const store = configureStore({ reducer: { tasks: tasksReducer, // other slices... }, }); export default store;
In Redux Toolkit, the
createSlice
function returns an object that includes both the reducer and action creators. The properties of this object are namedactions
andreducer
. Let's break down whattasksSlice.actions
andtasksSlice.reducer
mean:tasksSlice.actions
:This property contains all the action creators generated by
createSlice
. Each action creator is a function that, when called, produces an action object with a specific type and payload.In the example with the
tasksSlice
, the action creators areaddTask
,removeTask
,toggleTask
,setLoading
, andsetError
.You can use these action creators to dispatch actions in your application. For example:
import { addTask } from '../slices/tasksSlice'; dispatch(addTask({ id: 1, text: 'Complete homework' }));
tasksSlice.reducer
:This property contains the reducer function generated by
createSlice
. The reducer is responsible for handling actions and updating the state accordingly.In the example,
tasksSlice.reducer
is the function that specifies how the state should change in response to actions likeaddTask
,removeTask
, etc.You use this reducer when configuring your Redux store. For example:
import { configureStore } from '@reduxjs/toolkit'; import tasksReducer from '../slices/tasksSlice'; const store = configureStore({ reducer: { tasks: tasksReducer, // other slices... }, }); export default store;
By using tasksSlice.actions
and tasksSlice.reducer
, you get the benefits of encapsulation and cleaner code organization. It's a convenient way to access both the action creators and the reducer associated with a specific slice in your Redux store.
Use Selectors: To access data in the application, utilize the
useSelector
hook from react-redux. This allows you to grab any reactive value or slice in the store without the need for context or prop drilling.Selectors are used to retrieve some specific data of state
// TaskComponent.js import React from 'react'; import { useSelector } from 'react-redux'; import { selectAllTasks } from './selectors'; // Assuming selectors.js is in the same directory const TaskComponent = () => { // Use the useSelector hook to access the selected data from the Redux store const allTasks = useSelector(state => state.myTasks.tasks); return ( <div> <h2>All Tasks</h2> <ul> {allTasks.map(task => ( <li key={task.id}>{task.name}</li> ))} </ul> </div> ); }; export default TaskComponent;
Dispatch Actions: To change the application's data, dispatch actions to the store using the
useDispatch
hook. These actions typically include a name and a data payload, triggered by user interactions, such as button clicks.In Redux, actions are passed to the store by dispatching them. The process involves calling the
dispatch
function with an action or an action creator. Let's break down how this works:Dispatching an Action Directly: You can dispatch an action directly by creating an action object and passing it to the
dispatch
function. For example:const action = { type: 'ADD_TODO', payload: { id: 1, text: 'Buy groceries', }, }; dispatch(action);
In this case, you create an action object with a
type
property (indicating the type of action) and an optionalpayload
property carrying additional data.Using Action Creators: Action creators are functions that create and return action objects. You can use action creators for a more structured and reusable approach:
// Action creator const addTodo = (id, text) => ({ type: 'ADD_TODO', payload: { id, text, }, }); // Dispatching the action using the action creator dispatch(addTodo(1, 'Buy groceries'));
Here,
addTodo
is an action creator that takes parameters and returns an action object. Callingdispatch(addTodo(1, 'Buy groceries'))
dispatches the action to the store.
EXAMPLE:
// TaskComponent.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { selectAllTasks, addTask } from './selectors'; // Assuming selectors.js is in the same directory
const TaskComponent = () => {
// Use the useSelector hook to access the selected data from the Redux store
const allTasks = useSelector(selectAllTasks);
// Use the useDispatch hook to get access to the dispatch function
const dispatch = useDispatch();
// State for the new task input
const [newTask, setNewTask] = useState('');
// Handler for adding a new task
const handleAddTask = () => {
// Dispatch the addTask action with the new task
dispatch(addTask({ id: Date.now(), name: newTask }));
// Clear the input field
setNewTask('');
};
return (
<div>
<h2>All Tasks</h2>
<ul>
{allTasks.map(task => (
<li key={task.id}>{task.name}</li>
))}
</ul>
{/* Input for adding a new task */}
<div>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="Enter a new task"
/>
<button onClick={handleAddTask}>Add Task</button>
</div>
</div>
);
};
export default TaskComponent;
- Redux DevTools: Finally, serve your application and install the Redux DevTools browser extension. This tool enables you to inspect and debug the entire timeline of actions and state changes in your application, providing valuable insights for development and debugging.
Example Folder Structure
In a typical React-Redux application, you would commonly structure your folders to manage Redux-related files. While there isn't a strict rule about where to create the Redux store, it's a common practice to organize your Redux-related code in a dedicated folder. Here's a common project structure:
/src
/actions // Redux action creators
/reducers // Redux reducers
/constants // Action type constants
/store // Redux store configuration
/components // React components
/containers // React-Redux container components
In this structure:
actions
: Contains action creators, functions that create actions.reducers
: Contains Redux reducers, which specify how the state changes in response to actions.constants
: Contains constants for action types to ensure consistency.store
: This is where you might configure your Redux store. You can have a file, saystore.js
orconfigureStore.js
, where you create and configure your Redux store.
Here's a simplified example of what store.js
might look like:
// store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers'; // Assuming you have a rootReducer in the /reducers folder
import thunkMiddleware from 'redux-thunk'; // Example middleware
const store = createStore(
rootReducer,
applyMiddleware(thunkMiddleware)
);
export default store;
Remember that the folder structure can vary based on your project's specific needs and preferences. The key is to keep related Redux files organized in a way that makes sense for your application.