NextJS, Redux
minhthuong031103
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