Home About Projects Blogs 
MTH

2025 © All Rights Reserved.
Built with ♡ by MinhThuong
Say Hello

NextJS, Redux

anh

minhthuong031103

  • https://github.com/minhthuong031103
  • Jun 18, 2023
  • Cài redux và redux toolkit

    npm i react-redux @reduxjs/toolkit

    Sử dụng redux bằng cách tạo folder redux
    Xác định có bao nhiêu slice? Ở đây có 2, Cart và Payment
    cartSlice.ts

    import { createSlice, PayloadAction } from '@reduxjs/toolkit';
    
    import { Product } from '@/graphql/types';
    
    export interface CartState {
      productsInCart: Product[];
      total: number;
      cartOpen: boolean;
    }
    //we need the interface because we have the Product type in the
    //productsInCart array, it we not have the interface, we can not access
    //the Product type like quantity,price...
    const initialState: CartState = {
      productsInCart: [],
      total: 0,
      cartOpen: false,
    };
    
    export const cartSlice = createSlice({
      name: 'cart',
      initialState,
      reducers: {
        //payload action is a wrapper around the action object that contains the payload
        addItemToCart: (state, action: PayloadAction<Product>) => {
          const product = state.productsInCart.find(
            (product) => product.id === action.payload.id
          );
          if (!product) {
            state.productsInCart.push({ ...action.payload, quantity: 1 });
            state.total += action.payload.price; //hoac dung reduce
          } else {
            product.quantity++;
            state.total = state.productsInCart.reduce((total, product) => {
              return total + product.price * product.quantity;
            }, 0);
          }
        },
    
        deleteItemFromCart: (state, action: PayloadAction<Product>) => {
          const productIndex = state.productsInCart.findIndex(
            (product) => product.id === action.payload.id
          );
          //delete product
          if (state.productsInCart[productIndex].quantity === 1) {
            state.productsInCart.splice(productIndex, 1);
          } else {
            state.productsInCart[productIndex].quantity--;
          }
       //and update total
            state.total = state.productsInCart.reduce(
              (total, product) => total + product.price * product.quantity,
              0
            );
        },
    
        openCart: (state, action: PayloadAction<boolean>) => {
          state.cartOpen = action.payload;
        },
      },
    });
    
    export const { addItemToCart, deleteItemFromCart, openCart } =
      cartSlice.actions;
    
    export default cartSlice;
    
    //the actions are the functions that we can call to dispatch an action to the store
    //actions come from the reducers, and the reducers are the functions that actually update the state
    //in the reducers, the numbers of functions are equal to the numbers of actions
    
    //we export the cartSlice.reducer to use in the store.ts file, it means
    //because the store.ts file is the file that we create the store, and the store
    // is the place that we store the state, so we need to
    // export the reducer to the store.ts file
    // because the reducer is the function that update the state

    Tạo slice bằng CreateSlice
    trong đó cần 3 argument: name, initial value và reducers
    reducers là các function mà xíu nữa mình sẽ truyền vào dispatch khi gọi các component

    Các hàm này có 2 arguments, state và action: PayloadActionNhưng khi gọi dispatch thì chỉ cần truyền vào function 1 argument là state bởi vì các function này được export ra từ cartSlice.ations
    export Cart slice để lát gắn vào store

    paymentModalSlice.ts

    import { createSlice, PayloadAction } from '@reduxjs/toolkit';
    
    const initialState = {
      openMultistepModal: false,
    };
    
    const paymentModalSlice = createSlice({
      name: 'paymentModal',
      initialState,
      reducers: {
        openModal: (state, action: PayloadAction<boolean>) => {
          state.openMultistepModal = action.payload;
        },
      },
    });
    
    export const { openModal } = paymentModalSlice.actions;
    export default paymentModalSlice;

    store.ts

    import { configureStore } from '@reduxjs/toolkit';
    import cartSlice from './cartSlice';
    import paymentModalSlice from './paymentModalSlice';
    
    export const store = configureStore({
      reducer: {
        cart: cartSlice.reducer,
        payment: paymentModalSlice.reducer,
      },
      devTools: process.env.NODE_ENV !== 'production',
    });
    
    export type RootState = ReturnType<typeof store.getState>;
    export type AppDispatch = typeof store.dispatch;
    
    //Reducer của store là truyền các reducer của slices đã export ra 
    còn hai thằng type kia là để gắn type cho thằng Hook=> Hook khá quan trọng vì nó cung cấp 2 hooks useAppDispatch và useAppSelector

    hook.ts

    import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
    import type { RootState, AppDispatch } from './store';
    
    export const useAppDispatch = () => useDispatch<AppDispatch>();
    export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

    Vì xài nextjs nên server component không gắn trực tiếp vào được nên là tạo 1 component Provider

    'use client';
    
    import { store } from './store';  //store là thằng đã configure
    import { Provider } from 'react-redux';
    
    export function Providers({ children }: { children: React.ReactNode }) {
      return <Provider store={store}>{children}</Provider>;
    }

    giờ thì gắn nó vào layout

      <Providers>
            <body className={inter.className}>
              <MyNavbar />
              <Cart />
              {children}
              <MyFooter />
            </body>
          </Providers>

    Cách dùng:

    Giả sử muốn truy cập vào state và thay đổi state cartOpen
    => cartOpen nằm bên trong slice Cart
      const dispatch = useAppDispatch();
    
      const isCartOpen = useAppSelector((state) => state.cart.cartOpen);
    //Đầu tiên là lấy hàm dispatch để xíu có thể bỏ action vào để thay đổi state
    //useAppSelector để lấy ra state hiện tại bên trong 
    
    nếu muốn thay đổi =>
     dispatch(openCart(!isCartOpen)) //openCart là function đã được export từ slice.actions,
     mình truyền vào đó action payload, còn state không cần truyền, 
    nó sẽ tự lấy state dựa trên function mình gọi