REDUX 101: Implementation

REDUX 101: Implementation

PART III

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:

  1. Create a React App: Begin by creating a React application using your preferred tool.

  2. Install Redux Toolkit, React Redux: Install the Redux Toolkit, which simplifies Redux setup.

     npm i @reduxjs/toolkit react-redux
    
  3. 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: {},
     })
    
  4. 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')
     )
    
  5. 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 named actions and reducer. Let's break down what tasksSlice.actions and tasksSlice.reducer mean:

    1. 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 are addTask, removeTask, toggleTask, setLoading, and setError.

      • 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' }));
        
    2. 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 like addTask, 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.

  1. 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;
    
  2. 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:

    1. 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 optional payload property carrying additional data.

    2. 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. Calling dispatch(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;
  1. 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, say store.js or configureStore.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.

REFERENCE