Next and Redux
8 июля 2024 г.
Эта справочная статья подойдет для тех, что уже знает, как работает redux. В ней содержится только основная информация, как подключить redux в next.js
В проект необходимо установить:
react-redux
npm i @reduxjs/toolkit react-redux
next-redux-wrapper
npm i next-redux-wrapper
Документация: https://www.npmjs.com/package/next-redux-wrapper
react-redux
npm i react-redux
Документация: https://react-redux.js.org/
App Router
Создание слайса
Slice создаем в папке lib. В документации redux папка со slices названа features, вы можете назвать ее как хотите.
Импортируйте в файл createSlice из @reduxjs/toolkit. Если вы работаете с TypeSсcript, вам также понадобится тип PayloadAction. Структура слайса в next app router ничем не отличается от обычной. Прописываете initialState, actions и selectors, если необходимо.
1import { createSlice, PayloadAction } from "@reduxjs/toolkit";2import { TUserDataResponse } from "@/types";34type userState = {5 userData: TUserDataResponse;6};78const initialState = {9 userData: [],10} as userState;1112export const user = createSlice({13 name: "user",14 initialState,15 reducers: {16 setUserData: (17 state,18 action: PayloadAction<TUserDataResponse>19 ) => {20 state.userData = action.payload;21 },22 },23 selectors: {24 selectUserData: (state) => {25 return state.userData;26 },27 },28});2930export const {31 setUserData32} = user.actions;33export const {34 selectUserData,35} = user.selectors;36export default user.reducer;37
Создание store
Файл store создаем в папке lib. Импортируем в него configureStore из reduxjs/toolkit и создаем store.
Также импортируем в него reducer из слайса.
В примере ниже mainApi - это api файл rtk query.
1import { configureStore } from "@reduxjs/toolkit";2import { mainApi } from "./mainApi"3import userReducer from "./slices/userSlice"4export const makeStore = () =>5 configureStore({6 reducer: {7 [mainApi.reducerPath]: mainApi.reducer,8 user: userReducer9 },10 middleware: (gDM) => gDM().concat(mainApi.middleware),11 });1213export type AppStore = ReturnType<typeof makeStore>;14export type RootState = ReturnType<AppStore["getState"]>;15export type AppDispatch = AppStore["dispatch"];16
Redux provider
В папке app создайте файл, можно назвать его StoreProvider. В него импортируем Provider из react-redux и makeStore, AppStore из файла store.ts.
1"use client";2import { useRef } from "react";3import { Provider } from "react-redux";4import { makeStore, AppStore } from "../lib/store";56export default function StoreProvider({7 children,8}: {9 children: React.ReactNode;10}) {11 const storeRef = useRef<AppStore>();12 if (!storeRef.current) {13 storeRef.current = makeStore();14 }1516 return <Provider store={storeRef.current}>{children}</Provider>;17}
Затем, также в папке app, импортируйте StoreProvider в файл layout.tsx и оберните в него компоненты.
1import type { Metadata } from "next";2import { Inter } from "next/font/google";3import "./globals.css";4import { headers } from "next/headers";5import StoreProvider from "./StoreProvider";67export const runtime = "nodejs";8export const dynamic = "force-static";910const inter = Inter({ subsets: ["latin"] });1112export const metadata: Metadata = {13 title: """,14 description: "",15};1617export default function RootLayout({18 children,19}: Readonly<{20 children: React.ReactNode;21}>) {22 return (23 <html lang="en">24 <body className={`${inter.className}`}>25 <StoreProvider>26 {children}27 </StoreProvider>28 </body>29 </html>30 );31}32
Теперь вы можете использовать ваш redux в компонентах. Он работает также как и в react.
Использование в компонентах
Actions
В ваш компонент импортируйте useDispatch из react-redux и функцию action слайса. В примере выше - это setUserData.
1import { useDispatch } from "react-redux";2import { setUserData } from "@/lib/slices/userSlice";
Затем отправьте данные в redux store с помощью dispatch.
1const dispatch = useDispatch();23<...>45if (apiData) {6 dispatch(setUserData(apiData));7}
Selectors
Импортируйте useSelector из react-redux в ваш компонент. Также импортируйте функцию селекта из вашего слайса. в примере выше - это selectUserData.
1import { useSelector } from "react-redux";2import { selectUserData } from "@/lib/slices/userSlice";
Затем внутри вашего компонента извлеките данные из redux strore c помощью useSelector. Внутрь круглых скобок передаем функцию селекта.
12const userData = useSelector(selectUserData);
Готово, теперь данные можно использовать.
Pages router
Написано 15 июля 2023
Создание слайса стэйта
Документация: https://redux-toolkit.js.org/api/createSlice
Создайте файл и назовите его по названию отрывка стэйта, который он будет представлять. В примере это searchSlice.ts.
В файле укажите initialState и сам slice.
1import { createSlice } from "@reduxjs/toolkit";2import { AppState } from "../store";3import { HYDRATE } from "next-redux-wrapper";45// Type for our state6export interface SearchState {7 searchState: string;8}910// Initial state11const initialState: SearchState = {12 searchState: "",13};1415// Actual Slice16export const searchSlice = createSlice({17 name: "search",18 initialState,19 reducers: {20 [HYDRATE]: (state, action) => {21 return {22 ...state,23 ...action.payload,24 };25 },26 // Action to set the authentication status27 setSearchState: (state, action) => {28 state.searchState = action.payload;29 },30 },3132});3334export const { setSearchState } = searchSlice.actions;3536export const selectSearchState = (state: AppState) => state.search.searchState;3738export default searchSlice.reducer;39
HYDRATE - Это специальный reducer для next, который работает поверх существующего состояния, если оно есть.
Создание store
Импортируем slice в store.ts. При экспорте store оборачиваем его в createWrapper из next-redux-wrapper.
1import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";2import { searchSlice } from "./slices/searchSlice";3import { createWrapper } from "next-redux-wrapper";45const makeStore = () =>6 configureStore({7 reducer: {8 [searchSlice.name]: searchSlice.reducer,9 },10 devTools: true,11 });1213export type AppStore = ReturnType<typeof makeStore>;14export type AppState = ReturnType<AppStore["getState"]>;15export type AppThunk<ReturnType = void> = ThunkAction<16 ReturnType,17 AppState,18 unknown,19 Action20>;2122export const wrapper = createWrapper<AppStore>(makeStore);
Настройка _app.tsx
В файл _app.tsx импортируем wrapper из store.ts и Provider из react-redux.
Деструктурируем store и props из wrapper и передаем store через Provider.
1import { Provider } from "react-redux";2import { wrapper } from "../store/store";3import "../styles/_global.scss";45const MyApp = ({ categories, Component, ...rest }) => {6 const { store, props } = wrapper.useWrappedStore(rest);7 const { pageProps } = props;8 return (9 <Provider store={store}>10 <Component {...pageProps} />11 </Provider>12 );13};1415export default MyApp;
Использование в компонентах
Там, где необходимо установить стэйт используем useDispatch из react-redux и импортируем функцию action.
Там, где нужно использовать значение из стэйта, импортируем select function из слайса и useSelector из react-redux.
1import React, { useState } from "react";2import { useDispatch } from "react-redux";3import { selectSearchState } from "../store/slices/searchSlice";4import { setSearchState } from "../store/slices/searchSlice";5import { useSelector } from "react-redux";67const HomePage = () => {8 const dispatch = useDispatch();9 const searchState = useSelector(selectSearchState);10 const [value, setValue] = useState(searchState);1112 const onInputChange = (e) => {13 setValue(e.target.value);14 dispatch(setSearchState(e.target.value));15 };1617 return (18 <section className="section">19 <h1>Next and Redux</h1>2021 <div className="">22 <input23 type="text"24 className="input"25 value={value}26 onChange={onInputChange}27 placeholder="Введите что-нибудь"28 />2930 <p>searchState: {searchState}</p>31 </div>32 </section>33 );34};3536export default HomePage;