Redux Tutorial - share my experience regarding redux, react-redux and redux-toolkit

Overview

Redux Tutorial

1. Introduction to Redux

1.1 What is Redux & why Redux?

  • A small JS Library
  • for managing medium/large amount of states globally in your app.
  • useContext + useReducer Hook ideas will help you to understand redux.

1.2 Some common terms related to redux

  • React-redux: redux is used with some common packages such as react-redux
  • redux-toolkit : recommended way to write redux logic for building redux app easily and avoiding mistakes.
  • redux devtools extension: helps to debug redux app easily.

1.3 how redux works?

  • define state.
  • dispatch an Action.
  • Reducer update state based on Action Type.
  • store will update the view

Screenshot 2022-05-17 at 19 37 57

2. redux core concept

  • State: consider what states you want to manage

    // define states
    count: 0;
    const initialState = { count: 0 };
    const initialState2 = { users: [{ name: "anisul islam" }] };
  • Action: actions are object that have 2 things- type & payload

    // define constants
    const INCREMENT = "INCREMENT";
    const DECREMENT = "DECREMENT";
    const ADD_USER = "ADD_USER";
    
    // dispatch(Action)
    {
      type: INCREMENT,
    }
    {
      type: DECREMENT,
    }
    {
      type: ADD_USER,
      payload: {
        name: "rafiqul islam",
      }
    }
  • Reducer: reducers are pure function which handles all logic. it updates the state depends on action type

    // crate 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;
      }
    };
  • Store: It holds the states. It has 3 important methods- getState(), dispatch(), suscribe()

    // 4. store - getState(), dispatch(), subscribe()
    
    // create store
    const store = createStore(counterReducer);
    
    store.subscribe(() => {
      console.log(store.getState());
    });
    
    // dispatch action
    store.dispatch(incrementCounter());
    store.dispatch(incrementCounter());
    store.dispatch(incrementCounter());
    store.dispatch(decrementCounter());

3. Complete Counter App

  • example of counter app

    { return { type: INCREMENT, }; }; const decrementCounter = () => { return { type: DECREMENT, }; }; const resetCounter = () => { return { type: RESET, }; }; const counterReducer = (state = initialCounterState, action) => { switch (action.type) { case INCREMENT: return { ...state, count: state.count + 1, }; case DECREMENT: return { ...state, count: state.count - 1, }; case RESET: return { ...state, count: 0, }; default: state; } }; const store = createStore(counterReducer); store.subscribe(() => { console.log(store.getState()); }); store.dispatch(incrementCounter()); store.dispatch(incrementCounter()); store.dispatch(decrementCounter()); store.dispatch(resetCounter());">
    const { createStore } = require("redux");
    
    const INCREMENT = "INCREMENT";
    const DECREMENT = "DECREMENT";
    const RESET = "RESET";
    
    const initialCounterState = {
      count: 0,
    };
    
    const incrementCounter = () => {
      return {
        type: INCREMENT,
      };
    };
    const decrementCounter = () => {
      return {
        type: DECREMENT,
      };
    };
    const resetCounter = () => {
      return {
        type: RESET,
      };
    };
    
    const counterReducer = (state = initialCounterState, action) => {
      switch (action.type) {
        case INCREMENT:
          return {
            ...state,
            count: state.count + 1,
          };
        case DECREMENT:
          return {
            ...state,
            count: state.count - 1,
          };
        case RESET:
          return {
            ...state,
            count: 0,
          };
    
        default:
          state;
      }
    };
    
    const store = createStore(counterReducer);
    
    store.subscribe(() => {
      console.log(store.getState());
    });
    
    store.dispatch(incrementCounter());
    store.dispatch(incrementCounter());
    store.dispatch(decrementCounter());
    store.dispatch(resetCounter());

4. payload in action

  • example

    { return { type: GET_PRODUCTS, }; }; const addProductAction = (product) => { return { type: ADD_PRODUCTS, payload: product, }; }; const productsReducer = (state = initialProductState, action) => { switch (action.type) { case GET_PRODUCTS: return { ...state, }; case ADD_PRODUCTS: return { products: [...state.products, action.payload], numberOfProducts: state.numberOfProducts + 1, }; default: return state; } }; const store = createStore(productsReducer); store.subscribe(() => { console.log(store.getState()); }); store.dispatch(getProductAction()); store.dispatch(addProductAction("pen")); store.dispatch(addProductAction("pencil"));">
    const { createStore } = require("redux");
    
    const GET_PRODUCTS = "GET_PRODUCTS";
    const ADD_PRODUCTS = "ADD_PRODUCTS";
    
    const initialProductState = {
      products: ["sugar", "salt"],
      numberOfProducts: 2,
    };
    
    const getProductAction = () => {
      return {
        type: GET_PRODUCTS,
      };
    };
    const addProductAction = (product) => {
      return {
        type: ADD_PRODUCTS,
        payload: product,
      };
    };
    
    const productsReducer = (state = initialProductState, action) => {
      switch (action.type) {
        case GET_PRODUCTS:
          return {
            ...state,
          };
        case ADD_PRODUCTS:
          return {
            products: [...state.products, action.payload],
            numberOfProducts: state.numberOfProducts + 1,
          };
    
        default:
          return state;
      }
    };
    
    const store = createStore(productsReducer);
    
    store.subscribe(() => {
      console.log(store.getState());
    });
    
    store.dispatch(getProductAction());
    store.dispatch(addProductAction("pen"));
    store.dispatch(addProductAction("pencil"));

5. Multiple reducers & combine multiple reducers

  • example

    { return { type: GET_PRODUCTS, }; }; const addProductAction = (product) => { return { type: ADD_PRODUCTS, payload: product, }; }; // cart actions const getCartAction = () => { return { type: GET_CART_ITEMS, }; }; const addCartAction = (product) => { return { type: ADD_CART_ITEMS, payload: product, }; }; const productsReducer = (state = initialProductState, action) => { switch (action.type) { case GET_PRODUCTS: return { ...state, }; case ADD_PRODUCTS: return { products: [...state.products, action.payload], numberOfProducts: state.numberOfProducts + 1, }; default: return state; } }; const cartReducer = (state = initialCartState, action) => { switch (action.type) { case GET_CART_ITEMS: return { ...state, }; case ADD_CART_ITEMS: return { cart: [...state.cart, action.payload], numberOfProducts: state.numberOfProducts + 1, }; default: return state; } }; const rootReduer = combineReducers({ productR: productsReducer, cartR: cartReducer, }); const store = createStore(rootReduer); store.subscribe(() => { console.log(store.getState()); }); store.dispatch(getProductAction()); store.dispatch(addProductAction("pen")); store.dispatch(getCartAction()); store.dispatch(addCartAction("salt"));">
    const { createStore, combineReducers } = require("redux");
    
    // product constants
    const GET_PRODUCTS = "GET_PRODUCTS";
    const ADD_PRODUCTS = "ADD_PRODUCTS";
    
    // cart constants
    const GET_CART_ITEMS = "GET_CART_ITEMS";
    const ADD_CART_ITEMS = "ADD_CART_ITEMS";
    
    // product states
    const initialProductState = {
      products: ["sugar", "salt"],
      numberOfProducts: 2,
    };
    
    // cart states
    const initialCartState = {
      cart: ["sugar"],
      numberOfProducts: 1,
    };
    
    // product actions
    const getProductAction = () => {
      return {
        type: GET_PRODUCTS,
      };
    };
    const addProductAction = (product) => {
      return {
        type: ADD_PRODUCTS,
        payload: product,
      };
    };
    
    // cart actions
    const getCartAction = () => {
      return {
        type: GET_CART_ITEMS,
      };
    };
    const addCartAction = (product) => {
      return {
        type: ADD_CART_ITEMS,
        payload: product,
      };
    };
    
    const productsReducer = (state = initialProductState, action) => {
      switch (action.type) {
        case GET_PRODUCTS:
          return {
            ...state,
          };
        case ADD_PRODUCTS:
          return {
            products: [...state.products, action.payload],
            numberOfProducts: state.numberOfProducts + 1,
          };
    
        default:
          return state;
      }
    };
    
    const cartReducer = (state = initialCartState, action) => {
      switch (action.type) {
        case GET_CART_ITEMS:
          return {
            ...state,
          };
        case ADD_CART_ITEMS:
          return {
            cart: [...state.cart, action.payload],
            numberOfProducts: state.numberOfProducts + 1,
          };
    
        default:
          return state;
      }
    };
    
    const rootReduer = combineReducers({
      productR: productsReducer,
      cartR: cartReducer,
    });
    
    const store = createStore(rootReduer);
    
    store.subscribe(() => {
      console.log(store.getState());
    });
    
    store.dispatch(getProductAction());
    store.dispatch(addProductAction("pen"));
    store.dispatch(getCartAction());
    store.dispatch(addCartAction("salt"));

6. Middleware

  • for extra features, middlepoint of dispatching an action and handledby reducer, performing async tasks, login etc.

  • Example of popular redux middlewares packages: redux-logger, redux-thunk

  • npm install redux-logger

  • example

    { return { type: GET_PRODUCTS, }; }; const addProductAction = (product) => { return { type: ADD_PRODUCTS, payload: product, }; }; const productsReducer = (state = initialProductState, action) => { switch (action.type) { case GET_PRODUCTS: return { ...state, }; case ADD_PRODUCTS: return { products: [...state.products, action.payload], numberOfProducts: state.numberOfProducts + 1, }; default: return state; } }; const store = createStore(productsReducer, applyMiddleware(logger)); store.subscribe(() => { console.log(store.getState()); }); store.dispatch(getProductAction()); store.dispatch(addProductAction("pen"));">
    const { createStore, combineReducers, applyMiddleware } = require("redux");
    const { default: logger } = require("redux-logger");
    
    // product constants
    const GET_PRODUCTS = "GET_PRODUCTS";
    const ADD_PRODUCTS = "ADD_PRODUCTS";
    
    // product states
    const initialProductState = {
      products: ["sugar", "salt"],
      numberOfProducts: 2,
    };
    
    // product actions
    const getProductAction = () => {
      return {
        type: GET_PRODUCTS,
      };
    };
    const addProductAction = (product) => {
      return {
        type: ADD_PRODUCTS,
        payload: product,
      };
    };
    
    const productsReducer = (state = initialProductState, action) => {
      switch (action.type) {
        case GET_PRODUCTS:
          return {
            ...state,
          };
        case ADD_PRODUCTS:
          return {
            products: [...state.products, action.payload],
            numberOfProducts: state.numberOfProducts + 1,
          };
    
        default:
          return state;
      }
    };
    
    const store = createStore(productsReducer, applyMiddleware(logger));
    
    store.subscribe(() => {
      console.log(store.getState());
    });
    
    store.dispatch(getProductAction());
    store.dispatch(addProductAction("pen"));

7. API Calling - async actions using redux-thunk

  • example

    { return { type: GET_TODOS_REQUEST, }; }; const getTodosSuccess = (todos) => { return { type: GET_TODOS_SUCCESS, payload: todos, }; }; const getTodosFailed = (error) => { return { type: GET_TODOS_FAILED, payload: error, }; }; const todosReducer = (state = initialTodosState, action) => { switch (action.type) { case GET_TODOS_REQUEST: return { ...state, isLoading: true, }; case GET_TODOS_SUCCESS: return { ...state, todos: action.payload, isLoading: false, }; case GET_TODOS_FAILED: return { ...state, isLoading: false, error: action.payload, }; default: state; } }; // async action creator // thunk-middleware allows us to return a function isntead of obejct const fetchData = () => { return (dispatch) => { dispatch(getTodosRequest()); axios .get(TODOS_URL) .then((res) => { const todos = res.data; const titles = todos.map((todo) => todo.title); dispatch(getTodosSuccess(titles)); }) .catch((err) => { const error = err.message; dispatch(getTodosFailed(error)); }); }; }; const store = createStore(todosReducer, applyMiddleware(reduxThunk)); store.subscribe(() => { console.log(store.getState()); }); store.dispatch(fetchData());">
    // async actions - api calling
    // api url - https://jsonplaceholder.typicode.com/todos
    // middleware- redux-thunk
    // axios api
    
    const { default: axios } = require("axios");
    const { createStore, applyMiddleware } = require("redux");
    const reduxThunk = require("redux-thunk").default;
    
    // define constants
    const GET_TODOS_REQUEST = "GET_TODOS_REQUEST";
    const GET_TODOS_SUCCESS = "GET_TODOS_SUCCESS";
    const GET_TODOS_FAILED = "GET_TODOS_FAILED";
    const TODOS_URL = "https://jsonplaceholder.typicode.com/todos";
    
    // define state
    const initialTodosState = {
      todos: [],
      isLoading: false,
      error: null,
    };
    
    const getTodosRequest = () => {
      return {
        type: GET_TODOS_REQUEST,
      };
    };
    
    const getTodosSuccess = (todos) => {
      return {
        type: GET_TODOS_SUCCESS,
        payload: todos,
      };
    };
    const getTodosFailed = (error) => {
      return {
        type: GET_TODOS_FAILED,
        payload: error,
      };
    };
    
    const todosReducer = (state = initialTodosState, action) => {
      switch (action.type) {
        case GET_TODOS_REQUEST:
          return {
            ...state,
            isLoading: true,
          };
        case GET_TODOS_SUCCESS:
          return {
            ...state,
            todos: action.payload,
            isLoading: false,
          };
        case GET_TODOS_FAILED:
          return {
            ...state,
            isLoading: false,
            error: action.payload,
          };
    
        default:
          state;
      }
    };
    
    // async action creator
    // thunk-middleware allows us to return a function isntead of obejct
    const fetchData = () => {
      return (dispatch) => {
        dispatch(getTodosRequest());
        axios
          .get(TODOS_URL)
          .then((res) => {
            const todos = res.data;
            const titles = todos.map((todo) => todo.title);
            dispatch(getTodosSuccess(titles));
          })
          .catch((err) => {
            const error = err.message;
            dispatch(getTodosFailed(error));
          });
      };
    };
    
    const store = createStore(todosReducer, applyMiddleware(reduxThunk));
    
    store.subscribe(() => {
      console.log(store.getState());
    });
    
    store.dispatch(fetchData());

    8. React-redux counter example

    • install redux and react-redux package
    • we will make a counter app; first we will build with state and then we will do this with react-redux. example
      count + 1 count => count - 1 count => 0 */ const App = () => { const [count, setCount] = useState(0); const handleIncrement = () => { setCount((count) => count + 1); }; const handleReset = () => { setCount(0); }; const handleDecrement = () => { setCount((count) => count - 1); }; return (

      React Redux Example

      Count : {count}

      ); }; export default App; ">
         // App.js
         import React, { useState } from "react";
      
         // state - count:0
         // action - increment, decrement, reset
         /*
         reducer - handle logic for state update
          count => count + 1
          count => count - 1
          count => 0
         */
      
         const App = () => {
           const [count, setCount] = useState(0);
      
           const handleIncrement = () => {
             setCount((count) => count + 1);
           };
      
           const handleReset = () => {
             setCount(0);
           };
      
           const handleDecrement = () => {
             setCount((count) => count - 1);
           };
      
           return (
             <div>
               <h1>React Redux Example</h1>
               <h2>Count : {count}</h2>
               <button onClick={handleIncrement}>Increment</button>
               <button onClick={handleReset}>Reset</button>
               <button onClick={handleDecrement}>Decrement</button>
             </div>
           );
         };
      
         export default App;
         
  • now we will make the same counter app using react-redux

    { return { type: INCREMENT, }; }; export const resetCounter = () => { return { type: RESET, }; }; export const decrementCounter = () => { return { type: DECREMENT, }; }; // step 3: create reducers //services/reducers/counterReducer.js /* reducer - handle logic for state update count => count + 1 count => count - 1 count => 0 */ import { DECREMENT, INCREMENT, RESET } from "../constants/counterConstants"; const initialState = { count: 0 }; const counterReducer = (state = initialState, action) => { switch (action.type) { case INCREMENT: return { ...state, count: state.count + 1, }; case RESET: return { ...state, count: 0, }; case DECREMENT: return { ...state, count: state.count - 1, }; default: return state; } }; export default counterReducer; // step 4: create store // npm install redux // src/store.js import { createStore } from "redux"; import counterReducer from "./services/reducers/counterReducer"; const store = createStore(counterReducer); export default store; // step 5: provide store in index.js // npm install react-redux import React from "react"; import { createRoot } from "react-dom/client"; import { Provider } from "react-redux"; import App from "./App"; import store from "./store"; const container = document.getElementById("root"); const root = createRoot(container); root.render( ); // step 6: use store in anywhere in your app. for example in Counter.js import React from "react"; import { useDispatch, useSelector } from "react-redux"; import { incrementCounter, decrementCounter, resetCounter, } from "./services/actions/counterAction"; const Counter = () => { const count = useSelector((state) => state.count); const dispatch = useDispatch(); const handleIncrement = () => { dispatch(incrementCounter()); }; const handleReset = () => { dispatch(resetCounter()); }; const handleDecrement = () => { dispatch(decrementCounter()); }; return (

    React Redux Example

    Count : {count}

    ); }; export default Counter; // create actions & constants // create reducers // create & provide store // useSelector, useDispatch ">
      // step 1: create constants
      //services/constants/counterConstants.js
        export const INCREMENT = "INCREMENT";
        export const RESET = "RESET";
        export const DECREMENT = "DECREMENT";
    
      // step 2: create actions 
      //services/actions/counterActions.js
      // action - increment, decrement, reset
    
        import { DECREMENT, INCREMENT, RESET } from "../constants/counterConstants";
    
        export const incrementCounter = () => {
          return {
            type: INCREMENT,
          };
        };
    
        export const resetCounter = () => {
          return {
            type: RESET,
          };
        };
    
        export const decrementCounter = () => {
          return {
            type: DECREMENT,
          };
        };
        
        
        // step 3: create reducers 
        //services/reducers/counterReducer.js
            /*
          reducer - handle logic for state update
           count => count + 1
           count => count - 1
           count => 0
          */
          import { DECREMENT, INCREMENT, RESET } from "../constants/counterConstants";
    
          const initialState = { count: 0 };
    
          const counterReducer = (state = initialState, action) => {
            switch (action.type) {
              case INCREMENT:
                return {
                  ...state,
                  count: state.count + 1,
                };
              case RESET:
                return {
                  ...state,
                  count: 0,
                };
              case DECREMENT:
                return {
                  ...state,
                  count: state.count - 1,
                };
    
              default:
                return state;
            }
          };
    
          export default counterReducer;
    
        // step 4: create store
        // npm install redux
        // src/store.js
            import { createStore } from "redux";
            import counterReducer from "./services/reducers/counterReducer";
            const store = createStore(counterReducer);
            export default store;
            
         // step 5: provide store in index.js
         // npm install react-redux
            import React from "react";
            import { createRoot } from "react-dom/client";
            import { Provider } from "react-redux";
    
            import App from "./App";
            import store from "./store";
    
            const container = document.getElementById("root");
            const root = createRoot(container);
    
            root.render(
              <Provider store={store}>
                <App />
              </Provider>
            );
    
          // step 6: use store in anywhere in your app. for example in Counter.js
          import React from "react";
          import { useDispatch, useSelector } from "react-redux";
          import {
            incrementCounter,
            decrementCounter,
            resetCounter,
          } from "./services/actions/counterAction";
    
          const Counter = () => {
            const count = useSelector((state) => state.count);
            const dispatch = useDispatch();
    
            const handleIncrement = () => {
              dispatch(incrementCounter());
            };
    
            const handleReset = () => {
              dispatch(resetCounter());
            };
    
            const handleDecrement = () => {
              dispatch(decrementCounter());
            };
    
            return (
              <div>
                <h1>React Redux Example</h1>
                <h2>Count : {count}</h2>
                <button onClick={handleIncrement}>Increment</button>
                <button onClick={handleReset}>Reset</button>
                <button onClick={handleDecrement}>Decrement</button>
              </div>
            );
          };
    
          export default Counter;
    
          // create actions & constants
          // create reducers
          // create & provide store
          // useSelector, useDispatch
You might also like...
A chart monitor for Redux DevTools https://www.npmjs.com/package/redux-devtools-chart-monitor
A chart monitor for Redux DevTools https://www.npmjs.com/package/redux-devtools-chart-monitor

Redux DevTools Chart Monitor This package was merged into redux-devtools monorepo. Please refer to that repository for the latest updates, issues and

An i18n solution for React/Redux and React Native projects
An i18n solution for React/Redux and React Native projects

redux-react-i18n An i18n solution with plural forms support for Redux/React Workers of all countries, unite! Supported languages list with expected co

Ruthlessly simple bindings to keep react-router and redux in sync

Project Deprecated This project is no longer maintained. For your Redux - Router syncing needs with React Router 4+, please see one of these librari

Front-end of the movie application created with React.js and Redux
Front-end of the movie application created with React.js and Redux

# Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run:

This command line helps you create components, pages and even redux implementation for your react project
This command line helps you create components, pages and even redux implementation for your react project

react-help-create This command line helps you create components, pages and even redux implementation for your react project. How to install it? To ins

Single page application for tracking cryptocurrencies. Includes sorting, animations, graphs and more. Made using React & Redux.
Single page application for tracking cryptocurrencies. Includes sorting, animations, graphs and more. Made using React & Redux.

Crypto Tracker A single page application meant to keep track of the most popular crypto currencies status. The user can sort the coins by highest gain

React with Redux, action, dispatch, reducer, store setup and guide

This Project has Snippets for react with redux steup, process and how to implemenntation details src-components-HomeButtons has old approach src-co

Shop Cart Page Built Using React And Redux
Shop Cart Page Built Using React And Redux

Getting Started with react-redux-jest-shop Shop cart page with use: React,redux,redux-thunk, API,JEST, LTR Steps for run git clone or download react-r

Single Page Application that uses Rick and Morty API along with React & Redux
Single Page Application that uses Rick and Morty API along with React & Redux

Rick and Morty Rick and Morty Characters Web Page. Single Page Application that

Owner
Anisul Islam
Passionate teacher, done my M.Sc. in Software, Web & cloud at Tampere University, Finland.
Anisul Islam
A sample React TypeScript Redux Toolkit CRUD app

A sample React TypeScript Redux Toolkit CRUD app

Roger NDUTIYE 17 Sep 2, 2022
Redux-Toolkit example with React Hooks CRUD Application, Axios, Rest API, Bootstrap

Redux-Toolkit example with React Hooks CRUD Application, Axios, Rest API, Bootstrap

null 55 Sep 29, 2022
Notas de estudo sobre o workflow de implementação do React Redux Toolkit.

Resumo: Redux Toolkit Este projeto foi criado com Create React App, usando o Redux e Redux Toolkit como modelos. Documentação: https://redux-toolkit.j

Thomas Ferreira 7 Mar 30, 2022
Toolkit for implementing clean architecture using Redux

?? Toolkit for implementing clean architecture using Redux ?? ?? Focuses on achi

Joseph Garrone 36 Sep 21, 2022
Skeleton React App configured with Redux store along with redux-thunk, redux persist and form validation using formik and yup

Getting Started with React-Redux App Some Configrations Needed You guys need to modify the baseUrl (path to your server) in the server.js file that is

Usama Sarfraz 11 Jul 10, 2022
A lightweight state management library for react inspired by redux and react-redux

A lightweight state management library for react inspired by redux and react-redux

null 2 Sep 9, 2022
A simple app for study react with redux, redux saga and typescript.

React com Redux, Redux-Saga e TypeScript. ?? Uma aplicação simple para entender o funcionamento do Redux e a melhor maneira de utiliza-lo junto com o

João Marcos Belanga 1 May 24, 2022
Redux - Create forms using Redux And React

Exercício de fixação Vamos criar formulários utilizando Redux! \o/ Antes de inic

Márcio Júnior 2 Jul 21, 2022
A Higher Order Component using react-redux to keep form state in a Redux store

redux-form You build great forms, but do you know HOW users use your forms? Find out with Form Nerd! Professional analytics from the creator of Redux

Redux Form 12.6k Sep 22, 2022
redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state.

redux-immutable redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state. When Redux creat

Gajus Kuizinas 1.9k Sep 25, 2022