|
| 1 | +--- |
| 2 | +id: nextjs |
| 3 | +title: NextJS |
| 4 | +sidebar_label: NextJS |
| 5 | +hide_title: true |
| 6 | +--- |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +# NextJS Application Tutorial |
| 11 | + |
| 12 | +:::tip What You'll Learn |
| 13 | + |
| 14 | +- How to set up and use Redux Toolkit with the NextJS framework |
| 15 | + |
| 16 | +::: |
| 17 | + |
| 18 | +:::info Prerequisites |
| 19 | + |
| 20 | +- Familiarity with [ES6 syntax and features](https://www.taniarascia.com/es6-syntax-and-feature-overview/) |
| 21 | +- Knowledge of React terminology: [JSX](https://reactjs.org/docs/introducing-jsx.html), [State](https://reactjs.org/docs/state-and-lifecycle.html), [Function Components, Props](https://reactjs.org/docs/components-and-props.html), and [Hooks](https://reactjs.org/docs/hooks-intro.html) |
| 22 | +- Understanding of [Redux terms and concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow) |
| 23 | +- Working through the [Quick strt guide](./quick-start.mdx) is recommended |
| 24 | + |
| 25 | +::: |
| 26 | + |
| 27 | +## Introduction |
| 28 | + |
| 29 | +NextJS is a server side rendering framework for React that presents some unique challenges for using Redux properly. There are two architectures for a NextJS application; the Pages Router and the App Router. The Pages Router is the original architecture for NextJS. Using Redux with the Pages Router is well understood and handled primarily by the (next-redux-wrapper)[https://github.com/kirill-konshin/next-redux-wrapper]. This tutorial will focus on the App Router architecture as it is the new default architecture option for NextJS and it presents some unique challenges for using Redux properly. |
| 30 | + |
| 31 | +### How to Read This Tutorial |
| 32 | + |
| 33 | +This page assumes that you already have an exisiting NextJS application based on the App Router architecture. |
| 34 | + |
| 35 | +## The App Router Architecture and Redux |
| 36 | + |
| 37 | +The primary new feature of the NextJS App Router is the addition of support for React Server Components (RSCs). RSCs are a special type of React component that only renders on the server, as opposed to "client" components that render on **both** the client and the server. RSCs can be defined as `async` functions and return promises during rendering as they make async requests for data to render. |
| 38 | + |
| 39 | +RSCs abilitiy to block for data means that with the App Router you no longer have `getServerSideProps` to fetch data for rendering. Any component in the tree can make asychronous requests for data. While this is very convenient it also means thats if you define global variables (like the Redux store) they will be shared across requests. This is a problem because the Redux store could be contaminated with data from other requests. |
| 40 | + |
| 41 | +### Creating a Redux Store per Request |
| 42 | + |
| 43 | +Following along with the (Quick-Start guide)[./quick-start.mdx] we need to make some changes to the `app/store.js` file. The first change is to move from defining store as a global to defining a `createStore` function that returns a new store for each request. |
| 44 | + |
| 45 | +```ts title="src/app/store.ts" |
| 46 | +import { configureStore } from '@reduxjs/toolkit' |
| 47 | + |
| 48 | +export const createStore = () => |
| 49 | + configureStore({ |
| 50 | + reducer: {}, |
| 51 | + }) |
| 52 | + |
| 53 | +// Infer the type of createStore |
| 54 | +export type StoreType = ReturnType<typeof createStore> |
| 55 | +// Infer the `RootState` and `AppDispatch` types from the store itself |
| 56 | +export type RootState = ReturnType<StoreType['getState']> |
| 57 | +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} |
| 58 | +export type AppDispatch = StoreType['dispatch'] |
| 59 | +``` |
| 60 | +
|
| 61 | +To use this new `createStore` function we need to create a new "client" component that will create the store and share it using the React-Redux `Provider` component. |
| 62 | +
|
| 63 | +```ts title="src/app/StoreProvider.tsx" |
| 64 | +// file: app/store.ts noEmit |
| 65 | +import { configureStore } from '@reduxjs/toolkit' |
| 66 | + |
| 67 | +// highlight-start |
| 68 | +export const createStore = () => |
| 69 | + configureStore({ |
| 70 | + reducer: {}, |
| 71 | + }) |
| 72 | +// highlight-end |
| 73 | + |
| 74 | +// Infer the type of createStore |
| 75 | +// highlight-start |
| 76 | +export type StoreType = ReturnType<typeof createStore> |
| 77 | +// highlight-end |
| 78 | +// Infer the `RootState` and `AppDispatch` types from the store itself |
| 79 | +export type RootState = ReturnType<StoreType['getState']> |
| 80 | +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} |
| 81 | +export type AppDispatch = StoreType['dispatch'] |
| 82 | + |
| 83 | +// file: app/StoreProvider.tsx |
| 84 | +;('use client') |
| 85 | +import { useRef } from 'react' |
| 86 | +import { Provider } from 'react-redux' |
| 87 | +// highlight-start |
| 88 | +import { createStore } from './store' |
| 89 | +// highlight-end |
| 90 | + |
| 91 | +export default function StoreProvider({ |
| 92 | + children, |
| 93 | +}: { |
| 94 | + children: React.ReactNode |
| 95 | +}) { |
| 96 | + // highlight-start |
| 97 | + const storeRef = useRef<ReturnType<typeof createStore>>(createStore()) |
| 98 | + // highlight-end |
| 99 | + |
| 100 | + return <Provider store={storeRef.current}>{children}</Provider> |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +If you need to intialize the store with data from the parent component then define that data as a property and use an action on the slice to set the data in the store as shown below. |
| 105 | + |
| 106 | +```ts title="src/app/StoreProvider.tsx" |
| 107 | +// file: features/counter/counterSlice.ts noEmit |
| 108 | +import { createSlice } from '@reduxjs/toolkit' |
| 109 | +import type { PayloadAction } from '@reduxjs/toolkit' |
| 110 | + |
| 111 | +const counterSlice = createSlice({ |
| 112 | + name: 'counter', |
| 113 | + initialState: { |
| 114 | + value: 0, |
| 115 | + }, |
| 116 | + reducers: { |
| 117 | + setCount: (state, action: PayloadAction<number>) => { |
| 118 | + state.value = action.payload |
| 119 | + }, |
| 120 | + }, |
| 121 | +}) |
| 122 | + |
| 123 | +export const { setCount } = counterSlice.actions |
| 124 | +export default counterSlice.reducer |
| 125 | + |
| 126 | +// file: app/store.ts noEmit |
| 127 | +import { configureStore } from '@reduxjs/toolkit' |
| 128 | +import counterReducer from '../features/counter/counterSlice' |
| 129 | + |
| 130 | +export const createStore = () => |
| 131 | + configureStore({ |
| 132 | + reducer: { |
| 133 | + counter: counterReducer, |
| 134 | + }, |
| 135 | + }) |
| 136 | + |
| 137 | +// Infer the type of createStore |
| 138 | +export type StoreType = ReturnType<typeof createStore> |
| 139 | +// Infer the `RootState` and `AppDispatch` types from the store itself |
| 140 | +export type RootState = ReturnType<StoreType['getState']> |
| 141 | +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} |
| 142 | +export type AppDispatch = StoreType['dispatch'] |
| 143 | + |
| 144 | +// file: app/StoreProvider.tsx |
| 145 | +;('use client') |
| 146 | +import { useRef } from 'react' |
| 147 | +import { Provider } from 'react-redux' |
| 148 | +import { createStore } from './store' |
| 149 | +// highlight-start |
| 150 | +import { setCount } from '../features/counter/counterSlice' |
| 151 | +// highlight-end |
| 152 | + |
| 153 | +export default function StoreProvider({ |
| 154 | + count, |
| 155 | + children, |
| 156 | +}: { |
| 157 | + count: number |
| 158 | + children: React.ReactNode |
| 159 | +}) { |
| 160 | + const storeRef = useRef<ReturnType<typeof createStore> | null>(null) |
| 161 | + if (!storeRef.current) { |
| 162 | + storeRef.current = createStore() |
| 163 | + // highlight-start |
| 164 | + storeRef.current.dispatch(setCount(count)) |
| 165 | + // highlight-end |
| 166 | + } |
| 167 | + |
| 168 | + return <Provider store={storeRef.current}>{children}</Provider> |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +The next step is to include the `StoreProvider` in your layout component. This will ensure that the store is created for each request and that the store is not shared across requests. Use the store exactly as you would normally using the hooks provided by `react-redux`. |
| 173 | + |
| 174 | +## What You've Learned |
| 175 | + |
| 176 | +That was a brief overview of how to set up and use Redux Toolkit with the App Router: |
| 177 | + |
| 178 | +:::tip Summary |
| 179 | + |
| 180 | +- **Create a Redux store per request by using `configureStore` wrapped in a `createStore` function** |
| 181 | +- **Provide the Redux store to the React application components** using a "client" component |
| 182 | +- **Use the store as you normally would using the hooks provided in react-redux** |
| 183 | + |
| 184 | +## What's Next? |
| 185 | + |
| 186 | +We recommend going through [**the "Redux Essentials" and "Redux Fundamentals" tutorials in the Redux core docs**](https://redux.js.org/tutorials/index), which will give you a complete understanding of how Redux works, what Redux Toolkit does, and how to use it correctly. |
0 commit comments