anisul-Islam / redux-toolkit-doc

In this repo, I will share my experience regarding redux, react-redux and redux-toolkit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Redux-ReduxToolkit Tutorial

video playlist link here: https://youtube.com/playlist?list=PLgH5QX0i9K3pe7Z7ATcyLdUW3grE4Vfld

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

    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

    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

    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

    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

    // 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

      // 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

      // 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

9. API calling in react-redux

  • step 1: setup project & install packages npm install redux react-redux redux-thunk axios
  • step 2: define constants-> src/services/constants/todosConstant.js
export const GET_TODOS_REQUEST = "GET_TODOS_REQUEST";
export const GET_TODOS_SUCCESS = "GET_TODOS_SUCCESS";
export const GET_TODOS_FAILED = "GET_TODOS_FAILED";
  • step 3: create async action creator -> src/services/actions/todosAction.js

    import {
      GET_TODOS_FAILED,
      GET_TODOS_REQUEST,
      GET_TODOS_SUCCESS,
    } from "../constants/todosConstant";
    
    import axios from "axios";
    
    export const getAllTodos = () => async (dispatch) => {
      dispatch({ type: GET_TODOS_REQUEST });
      try {
        const res = await axios.get("https://jsonplaceholder.typicode.com/posts");
        dispatch({ type: GET_TODOS_SUCCESS, payload: res.data });
      } catch (error) {
        dispatch({ type: GET_TODOS_FAILED, payload: error.message });
      }
    };
  • step 4: create reducer -> src/services/reducers/todosReducer.js

    import {
      GET_TODOS_FAILED,
      GET_TODOS_REQUEST,
      GET_TODOS_SUCCESS,
    } from "../constants/todosConstant";
    
    const initialState = {
      isLoading: false,
      todos: [],
      error: null,
    };
    
    export const todosReducer = (state = initialState, action) => {
      switch (action.type) {
        case GET_TODOS_REQUEST:
          return {
            ...state,
            isLoading: true,
          };
        case GET_TODOS_SUCCESS:
          return {
            isLoading: false,
            todos: action.payload,
            error: null,
          };
        case GET_TODOS_FAILED:
          return {
            isLoading: false,
            todos: [],
            error: action.payload,
          };
    
        default:
          return state;
      }
    };
  • step 5: create store -> src/store.js

    import { applyMiddleware, createStore } from "redux";
    import thunk from "redux-thunk";
    import { todosReducer } from "./services/reducers/todosReducer";
    
    const store = createStore(todosReducer, applyMiddleware(thunk));
    export default store;
  • step 6: provide store -> src/index.js

    import React from "react";
    import ReactDOM from "react-dom/client";
    import "./index.css";
    import App from "./App";
    import reportWebVitals from "./reportWebVitals";
    
    import { Provider } from "react-redux";
    import store from "./store";
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    
    root.render(
      <Provider store={store}>
        <App />
      </Provider>
    );
    reportWebVitals();
  • step 7: use store -> src/components/Todos.js

    import React, { useEffect } from "react";
    import { useDispatch, useSelector } from "react-redux";
    import { getAllTodos } from "../services/actions/todosAction";
    
    const Todos = () => {
      const { isLoading, todos, error } = useSelector((state) => state);
    
      console.log(isLoading);
    
      const dispatch = useDispatch();
      useEffect(() => {
        dispatch(getAllTodos());
      }, []);
    
      return (
        <div>
          <h2>Todos App</h2>
          {isLoading && <h3>Loading ...</h3>}
          {error && <h3>{error.message}</h3>}
          <section>
            {todos &&
              todos.map((todo) => {
                return (
                  <article key={todo.id}>
                    <h4>{todo.id}</h4>
                    <p>{todo.title}</p>
                  </article>
                );
              })}
          </section>
        </div>
      );
    };
    export default Todos;
  • step 8: adding style -> src/App.css

    section {
      display: grid;
      grid-gap: 0.5rem;
      padding: 1rem;
    }
    
    article {
      background-color: #293462;
      color: white;
      padding: 0.5rem;
    }
    
    @media (min-width: 600px) {
      section {
        grid-template-columns: auto auto;
      }
    }
    @media (min-width: 768px) {
      section {
        grid-template-columns: auto auto auto;
      }
    }

10. redux-toolkit counter app

  • step1: install packages -> npm install @reduxjs/toolkit react-redux

  • step2: create a recommended folder structure for redux-toolkit - features (contains individual feature of our app) - app (contains store of our app)

  • step3: create slice. collection of logic for a feature is called slices in redux. - src/features/counter/counterSlice

    import { createSlice } from "@reduxjs/toolkit";
    
    // state: count:0
    // increment, decrement, reset
    
    // const incrementCounter = () => {
    //   return { type: "INCREMENT" };
    // };
    
    export const counterSlice = createSlice({
      name: "counter",
      initialState: { count: 0 },
      reducers: {
        increment: (state) => {
          state.count = state.count + 1;
        },
        decrement: (state) => {
          state.count = state.count - 1;
        },
        reset: (state) => {
          state.count = 0;
        },
        increaseByAmount: (state, action) => {
          state.count = state.count + action.payload;
        },
      },
    });
    
    // export reducer and action createor
    // Action creators are generated for each case reducer function
    export const { increment, decrement, reset, increaseByAmount } =
      counterSlice.actions;
    
    export default counterSlice.reducer;
  • step4: create store -> app/store.js

    import { configureStore } from "@reduxjs/toolkit";
    
    import counterReducer from "../features/counter/counterSlice";
    
    const store = configureStore({
      reducer: {
        counter: counterReducer,
      },
    });
    export default store;
  • step5: provide store in root file -> src/index.js

    import React from "react";
    import ReactDOM from "react-dom/client";
    import "./index.css";
    import App from "./App";
    import reportWebVitals from "./reportWebVitals";
    import { Provider } from "react-redux";
    
    import store from "./app/store";
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(
      <Provider store={store}>
        <App />
      </Provider>
    );
  • step6: use store & dispatch actions

    import React from "react";
    import { useDispatch, useSelector } from "react-redux";
    import {
      decrement,
      increaseByAmount,
      increment,
      reset,
    } from "./counterSlice";
    
    const CounterView = () => {
      const count = useSelector((state) => state.counter.count);
    
      const dispatch = useDispatch();
    
      return (
        <div>
          <h2>Counter: {count}</h2>
          <button
            onClick={() => {
              dispatch(increment());
            }}
          >
            Increment
          </button>
          <button
            onClick={() => {
              dispatch(reset());
            }}
          >
            reset
          </button>
          <button
            onClick={() => {
              dispatch(decrement());
            }}
          >
            Decrement
          </button>
          <button
            onClick={() => {
              dispatch(increaseByAmount(5));
            }}
          >
            IncrementBy5
          </button>
        </div>
      );
    };
    
    export default CounterView;

11. API calling using redux-toolkit in react

12. CRUD APP using redux-toolkit in react


About

In this repo, I will share my experience regarding redux, react-redux and redux-toolkit


Languages

Language:JavaScript 100.0%