Update Redux example to use Toolkit + update dependencies. (#29104)
After talking with markerikson, I've made some updates to the Redux examples: - Updated the `with-redux` example to use the Redux Toolkit + TypeScript example - Updated dependencies of `with-redux` to work with latest testing setup - Removed `with-redux-toolkit` in favor of `with-redux` - Removed `with-redux-toolkit-typescript` in favor of `with-redux` - Removed `with-redux-code-splitting` in favor of `with-redux`
This commit is contained in:
parent
6a9cda5726
commit
dd4215b81e
55 changed files with 27 additions and 971 deletions
34
examples/with-redux-code-splitting/.gitignore
vendored
34
examples/with-redux-code-splitting/.gitignore
vendored
|
@ -1,34 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
|
@ -1,29 +0,0 @@
|
|||
# Redux with code splitting example
|
||||
|
||||
Redux uses single store per application and usually it causes problems for code splitting when you want to load actions and reducers used on the current page only.
|
||||
|
||||
This example utilizes [fast-redux](https://github.com/dogada/fast-redux) to split Redux's actions and reducers across pages. In result each page's javascript bundle contains only code that is used on the page. When user navigates to a new page, its actions and reducers are connected to the single shared application store.
|
||||
|
||||
## Preview
|
||||
|
||||
Preview the example live on [StackBlitz](http://stackblitz.com/):
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-redux-code-splitting)
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-redux-code-splitting&project-name=with-redux-code-splitting&repository-name=with-redux-code-splitting)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example with-redux-code-splitting with-redux-code-splitting-app
|
||||
# or
|
||||
yarn create next-app --example with-redux-code-splitting with-redux-code-splitting-app
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
|
@ -1,15 +0,0 @@
|
|||
import { createStore, applyMiddleware } from 'redux'
|
||||
import { composeWithDevTools } from 'redux-devtools-extension'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import withRedux from 'next-redux-wrapper'
|
||||
import { rootReducer } from 'fast-redux'
|
||||
|
||||
export const initStore = (initialState = {}) => {
|
||||
return createStore(
|
||||
rootReducer,
|
||||
initialState,
|
||||
composeWithDevTools(applyMiddleware(thunkMiddleware))
|
||||
)
|
||||
}
|
||||
|
||||
export const reduxPage = (comp) => withRedux(initStore)(comp)
|
|
@ -1,39 +0,0 @@
|
|||
import React from 'react'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { connect } from 'react-redux'
|
||||
import { namespaceConfig } from 'fast-redux'
|
||||
import Link from 'next/link'
|
||||
|
||||
const DEFAULT_STATE = { version: 1 }
|
||||
|
||||
const { actionCreator, getState: getAboutState } = namespaceConfig(
|
||||
'about',
|
||||
DEFAULT_STATE
|
||||
)
|
||||
|
||||
const bumpVersion = actionCreator('bumpVersion', function (state, increment) {
|
||||
return { ...state, version: state.version + increment }
|
||||
})
|
||||
|
||||
const About = ({ version, bumpVersion }) => (
|
||||
<div>
|
||||
<h1>About us</h1>
|
||||
<h3>Current version: {version}</h3>
|
||||
<p>
|
||||
<button onClick={(e) => bumpVersion(1)}>Bump version!</button>
|
||||
</p>
|
||||
<Link href="/">
|
||||
<a>Homepage</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return getAboutState(state, 'version')
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators({ bumpVersion }, dispatch)
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(About)
|
|
@ -1,39 +0,0 @@
|
|||
import React from 'react'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { connect } from 'react-redux'
|
||||
import { namespaceConfig } from 'fast-redux'
|
||||
import Link from 'next/link'
|
||||
|
||||
const DEFAULT_STATE = { build: 1 }
|
||||
|
||||
const { actionCreator, getState: getHomepageState } = namespaceConfig(
|
||||
'homepage',
|
||||
DEFAULT_STATE
|
||||
)
|
||||
|
||||
const bumpBuild = actionCreator(function bumpBuild(state, increment) {
|
||||
return { ...state, build: state.build + increment }
|
||||
})
|
||||
|
||||
const Homepage = ({ build, bumpBuild }) => (
|
||||
<div>
|
||||
<h1>Homepage</h1>
|
||||
<h3>Current build: {build}</h3>
|
||||
<p>
|
||||
<button onClick={(e) => bumpBuild(1)}>Bump build!</button>
|
||||
</p>
|
||||
<Link href="/about">
|
||||
<a>About Us</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return getHomepageState(state)
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators({ bumpBuild }, dispatch)
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Homepage)
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-redux": "~0.3.0",
|
||||
"next": "latest",
|
||||
"next-redux-wrapper": "~1.3.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "~5.0.5",
|
||||
"redux": "~3.7.2",
|
||||
"redux-devtools-extension": "~2.13.2",
|
||||
"redux-thunk": "~2.2.0"
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
import { reduxPage } from '../config/redux'
|
||||
import About from '../containers/about'
|
||||
|
||||
export default reduxPage(About)
|
|
@ -1,4 +0,0 @@
|
|||
import { reduxPage } from '../config/redux'
|
||||
import Homepage from '../containers/homepage'
|
||||
|
||||
export default reduxPage(Homepage)
|
|
@ -1,34 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
|
@ -1,23 +1 @@
|
|||
# Redux Toolkit TypeScript Example
|
||||
|
||||
This example shows how to integrate Next.js with [Redux Toolkit](https://redux-toolkit.js.org).
|
||||
|
||||
The **Redux Toolkit** is a standardized way to write Redux logic (create actions and reducers, setup the store with some default middlewares like redux devtools extension). This example demonstrates each of these features with Next.js
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-redux-toolkit-typescript&project-name=with-redux-toolkit&repository-name=with-redux-toolkit)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example with-redux-toolkit-typescript with-redux-toolkit-app
|
||||
# or
|
||||
yarn create next-app --example with-redux-toolkit-typescript with-redux-toolkit-app
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||
This example has been moved to [with-redux](https://github.com/vercel/next.js/tree/canary/examples/with-redux).
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"type-check": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.3.6",
|
||||
"next": "latest",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.0.0",
|
||||
"@testing-library/react": "^11.0.0",
|
||||
"@testing-library/user-event": "^13.0.0",
|
||||
"@types/jest": "^26.0.0",
|
||||
"@types/node": "14.14.35",
|
||||
"@types/react": "17.0.3",
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@types/react-redux": "7.1.16",
|
||||
"jest": "^26.0.0",
|
||||
"jest-css-modules-transform": "^4.2.0",
|
||||
"ts-jest": "^26.0.0",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.3.4"
|
||||
}
|
||||
}
|
34
examples/with-redux-toolkit/.gitignore
vendored
34
examples/with-redux-toolkit/.gitignore
vendored
|
@ -1,34 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
|
@ -1,29 +1 @@
|
|||
# Redux Toolkit example
|
||||
|
||||
This example shows how to integrate Next.js with [Redux Toolkit](https://redux-toolkit.js.org).
|
||||
|
||||
The **Redux Toolkit** is intended to be the standard way to write Redux logic (create actions and reducers, setup the store with some default middlewares like redux devtools extension). This example demonstrates each of these features with Next.js
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-redux-toolkit&project-name=with-redux-toolkit&repository-name=with-redux-toolkit)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example with-redux-toolkit with-redux-toolkit-app
|
||||
# or
|
||||
yarn create next-app --example with-redux-toolkit with-redux-toolkit-app
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||
|
||||
### TypeScript Setup (optional)
|
||||
|
||||
If you haven't already added [TypeScript](https://www.typescriptlang.org/) to your project, follow the [steps in the Next.js documentation](https://nextjs.org/docs/basic-features/typescript). If you are new to TypeScript, go through the Next.js [learning lesson on TypeScript](https://nextjs.org/learn/excel/TypeScript).
|
||||
|
||||
Once TypeScript is added, follow the instructions given on the Redux Toolkit [documentation](https://redux-toolkit.js.org/tutorials/TypeScript) to set up and use Redux Toolkit and React-Redux with TypeScript
|
||||
This example has been moved to [with-redux](https://github.com/vercel/next.js/tree/canary/examples/with-redux).
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "1.5.0",
|
||||
"next": "latest",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "7.2.2"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 3.5 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><g fill="#764ABC"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,8 +0,0 @@
|
|||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import counterReducer from '../features/counter/counterSlice'
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
counter: counterReducer,
|
||||
},
|
||||
})
|
|
@ -1,60 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import {
|
||||
decrement,
|
||||
increment,
|
||||
incrementByAmount,
|
||||
incrementAsync,
|
||||
selectCount,
|
||||
} from './counterSlice'
|
||||
import styles from './Counter.module.css'
|
||||
|
||||
export function Counter() {
|
||||
const count = useSelector(selectCount)
|
||||
const dispatch = useDispatch()
|
||||
const [incrementAmount, setIncrementAmount] = useState('2')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.row}>
|
||||
<button
|
||||
className={styles.button}
|
||||
aria-label="Increment value"
|
||||
onClick={() => dispatch(increment())}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<span className={styles.value}>{count}</span>
|
||||
<button
|
||||
className={styles.button}
|
||||
aria-label="Decrement value"
|
||||
onClick={() => dispatch(decrement())}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<input
|
||||
className={styles.textbox}
|
||||
aria-label="Set increment amount"
|
||||
value={incrementAmount}
|
||||
onChange={(e) => setIncrementAmount(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className={styles.button}
|
||||
onClick={() =>
|
||||
dispatch(incrementByAmount(Number(incrementAmount) || 0))
|
||||
}
|
||||
>
|
||||
Add Amount
|
||||
</button>
|
||||
<button
|
||||
className={styles.asyncButton}
|
||||
onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))}
|
||||
>
|
||||
Add Async
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.row:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 78px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
margin-top: 2px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
.button {
|
||||
appearance: none;
|
||||
background: none;
|
||||
font-size: 32px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
outline: none;
|
||||
border: 2px solid transparent;
|
||||
color: rgb(112, 76, 182);
|
||||
padding-bottom: 4px;
|
||||
cursor: pointer;
|
||||
background-color: rgba(112, 76, 182, 0.1);
|
||||
border-radius: 2px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
font-size: 32px;
|
||||
padding: 2px;
|
||||
width: 64px;
|
||||
text-align: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
border: 2px solid rgba(112, 76, 182, 0.4);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
background-color: rgba(112, 76, 182, 0.2);
|
||||
}
|
||||
|
||||
.asyncButton {
|
||||
composes: button;
|
||||
position: relative;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.asyncButton:after {
|
||||
content: '';
|
||||
background-color: rgba(112, 76, 182, 0.15);
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
transition: width 1s linear, opacity 0.5s ease 1s;
|
||||
}
|
||||
|
||||
.asyncButton:active:after {
|
||||
width: 0%;
|
||||
opacity: 1;
|
||||
transition: 0s;
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
const initialState = {
|
||||
value: 0,
|
||||
}
|
||||
|
||||
export const counterSlice = createSlice({
|
||||
name: 'counter',
|
||||
initialState,
|
||||
reducers: {
|
||||
increment: (state) => {
|
||||
// Redux Toolkit allows us to write "mutating" logic in reducers. It
|
||||
// doesn't actually mutate the state because it uses the Immer library,
|
||||
// which detects changes to a "draft state" and produces a brand new
|
||||
// immutable state based on those changes
|
||||
state.value += 1
|
||||
},
|
||||
decrement: (state) => {
|
||||
state.value -= 1
|
||||
},
|
||||
// Use the PayloadAction type to declare the contents of `action.payload`
|
||||
incrementByAmount: (state, action) => {
|
||||
state.value += action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { increment, decrement, incrementByAmount } = counterSlice.actions
|
||||
|
||||
// The function below is called a thunk and allows us to perform async logic. It
|
||||
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
|
||||
// will call the thunk with the `dispatch` function as the first argument. Async
|
||||
// code can then be executed and other actions can be dispatched
|
||||
export const incrementAsync = (amount) => (dispatch) => {
|
||||
setTimeout(() => {
|
||||
dispatch(incrementByAmount(amount))
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// The function below is called a selector and allows us to select a value from
|
||||
// the state. Selectors can also be defined inline where they're used instead of
|
||||
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
|
||||
export const selectCount = (state) => state.counter.value
|
||||
|
||||
export default counterSlice.reducer
|
|
@ -1,13 +0,0 @@
|
|||
import { Provider } from 'react-redux'
|
||||
import { store } from '../app/store'
|
||||
import '../styles/globals.css'
|
||||
|
||||
const MyApp = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Component {...pageProps} />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyApp
|
|
@ -1,59 +0,0 @@
|
|||
import { Counter } from '../features/counter/Counter'
|
||||
import styles from '../styles/Home.module.css'
|
||||
import Head from 'next/head'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Head>
|
||||
<title>Redux Toolkit</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<header className={styles.header}>
|
||||
<img src="/logo.svg" className={styles.logo} alt="logo" />
|
||||
<Counter />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<span>
|
||||
<span>Learn </span>
|
||||
<a
|
||||
className={styles.link}
|
||||
href="https://reactjs.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
React
|
||||
</a>
|
||||
<span>, </span>
|
||||
<a
|
||||
className={styles.link}
|
||||
href="https://redux.js.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Redux
|
||||
</a>
|
||||
<span>, </span>
|
||||
<a
|
||||
className={styles.link}
|
||||
href="https://redux-toolkit.js.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Redux Toolkit
|
||||
</a>
|
||||
,<span> and </span>
|
||||
<a
|
||||
className={styles.link}
|
||||
href="https://react-redux.js.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
React Redux
|
||||
</a>
|
||||
</span>
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
.container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
}
|
||||
|
||||
.link {
|
||||
color: rgb(112, 76, 182);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.logo {
|
||||
animation: logo-float infinite 3s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes logo-float {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(10px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
|
@ -1,24 +1,8 @@
|
|||
# Redux example
|
||||
# Redux Toolkit TypeScript Example
|
||||
|
||||
This example shows how to integrate Redux in Next.js.
|
||||
This example shows how to integrate Next.js with [Redux Toolkit](https://redux-toolkit.js.org).
|
||||
|
||||
Usually splitting your app state into `pages` feels natural but sometimes you'll want to have global state for your app. This is an example on how you can use Redux that also works with Next.js's universal rendering approach.
|
||||
|
||||
In the first example we are going to display a digital clock that updates every second. The first render is happening in the server and then the browser will take over. To illustrate this, the server rendered clock will have a different background color (black) than the client one (grey).
|
||||
|
||||
To illustrate SSG and SSR, go to `/ssg` and `/ssr`, those pages are using Next.js data fetching methods to get the date in the server and return it as props to the page, and then the browser will hydrate the store and continue updating the date.
|
||||
|
||||
The trick here for supporting universal Redux is to separate the cases for the client and the server. When we are on the server we want to create a new store every time, otherwise different users data will be mixed up. If we are in the client we want to use always the same store. That's what we accomplish on `store.js`.
|
||||
|
||||
All components have access to the Redux store using `useSelector`, `useDispatch` or `connect` from `react-redux`.
|
||||
|
||||
On the server side every request initializes a new store, because otherwise different user data can be mixed up. On the client side the same store is used, even between page changes.
|
||||
|
||||
## Preview
|
||||
|
||||
Preview the example live on [StackBlitz](http://stackblitz.com/):
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-redux)
|
||||
The **Redux Toolkit** is a standardized way to write Redux logic (create actions and reducers, setup the store with some default middlewares like redux devtools extension). This example demonstrates each of these features with Next.js
|
||||
|
||||
## Deploy your own
|
||||
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { useSelector, shallowEqual } from 'react-redux'
|
||||
|
||||
const useClock = () => {
|
||||
return useSelector(
|
||||
(state) => ({
|
||||
lastUpdate: state.lastUpdate,
|
||||
light: state.light,
|
||||
}),
|
||||
shallowEqual
|
||||
)
|
||||
}
|
||||
|
||||
const formatTime = (time) => {
|
||||
// cut off except hh:mm:ss
|
||||
return new Date(time).toJSON().slice(11, 19)
|
||||
}
|
||||
|
||||
const Clock = () => {
|
||||
const { lastUpdate, light } = useClock()
|
||||
return (
|
||||
<div className={light ? 'light' : ''}>
|
||||
{formatTime(lastUpdate)}
|
||||
<style jsx>{`
|
||||
div {
|
||||
padding: 15px;
|
||||
display: inline-block;
|
||||
color: #82fa58;
|
||||
font: 50px menlo, monaco, monospace;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.light {
|
||||
background-color: #999;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Clock
|
|
@ -1,35 +0,0 @@
|
|||
import { useSelector, useDispatch } from 'react-redux'
|
||||
|
||||
const useCounter = () => {
|
||||
const count = useSelector((state) => state.count)
|
||||
const dispatch = useDispatch()
|
||||
const increment = () =>
|
||||
dispatch({
|
||||
type: 'INCREMENT',
|
||||
})
|
||||
const decrement = () =>
|
||||
dispatch({
|
||||
type: 'DECREMENT',
|
||||
})
|
||||
const reset = () =>
|
||||
dispatch({
|
||||
type: 'RESET',
|
||||
})
|
||||
return { count, increment, decrement, reset }
|
||||
}
|
||||
|
||||
const Counter = () => {
|
||||
const { count, increment, decrement, reset } = useCounter()
|
||||
return (
|
||||
<div>
|
||||
<h1>
|
||||
Count: <span>{count}</span>
|
||||
</h1>
|
||||
<button onClick={increment}>+1</button>
|
||||
<button onClick={decrement}>-1</button>
|
||||
<button onClick={reset}>Reset</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Counter
|
|
@ -1,26 +0,0 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
const Nav = () => {
|
||||
return (
|
||||
<nav>
|
||||
<Link href="/">
|
||||
<a>Index</a>
|
||||
</Link>
|
||||
<Link href="/ssg">
|
||||
<a>SSG</a>
|
||||
</Link>
|
||||
<Link href="/ssr">
|
||||
<a>SSR</a>
|
||||
</Link>
|
||||
<style jsx>
|
||||
{`
|
||||
a {
|
||||
margin-right: 25px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default Nav
|
|
@ -1,26 +0,0 @@
|
|||
import { useDispatch } from 'react-redux'
|
||||
import useInterval from '../lib/useInterval'
|
||||
import Clock from './clock'
|
||||
import Counter from './counter'
|
||||
import Nav from './nav'
|
||||
|
||||
export default function Page() {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// Tick the time every second
|
||||
useInterval(() => {
|
||||
dispatch({
|
||||
type: 'TICK',
|
||||
light: true,
|
||||
lastUpdate: Date.now(),
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<Clock />
|
||||
<Counter />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -6,6 +6,7 @@ const config: InitialOptionsTsJest = {
|
|||
transform: {
|
||||
'.+\\.(css|styl|less|sass|scss)$': 'jest-css-modules-transform',
|
||||
},
|
||||
testEnvironment: 'jsdom',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.test.json',
|
|
@ -1,19 +0,0 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
|
||||
// https://overreacted.io/making-setinterval-declarative-with-react-hooks/
|
||||
const useInterval = (callback, delay) => {
|
||||
const savedCallback = useRef()
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback
|
||||
}, [callback])
|
||||
useEffect(() => {
|
||||
const handler = (...args) => savedCallback.current(...args)
|
||||
|
||||
if (delay !== null) {
|
||||
const id = setInterval(handler, delay)
|
||||
return () => clearInterval(id)
|
||||
}
|
||||
}, [delay])
|
||||
}
|
||||
|
||||
export default useInterval
|
|
@ -3,14 +3,30 @@
|
|||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
"start": "next start",
|
||||
"type-check": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.3.6",
|
||||
"next": "latest",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.1.0",
|
||||
"redux": "^3.6.0",
|
||||
"redux-devtools-extension": "^2.13.2"
|
||||
"react-redux": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.0.0",
|
||||
"@testing-library/react": "^12.1.0",
|
||||
"@testing-library/user-event": "^13.0.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.9.1",
|
||||
"@types/react": "^17.0.21",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-redux": "^7.1.18",
|
||||
"jest": "^27.2.0",
|
||||
"jest-css-modules-transform": "^4.2.0",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.3.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { Provider } from 'react-redux'
|
||||
import { useStore } from '../store'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
const store = useStore(pageProps.initialReduxState)
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Component {...pageProps} />
|
||||
</Provider>
|
||||
)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import Page from '../components/page'
|
||||
|
||||
export default function Index() {
|
||||
return <Page />
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import Page from '../components/page'
|
||||
|
||||
export default function SSG() {
|
||||
return <Page />
|
||||
}
|
||||
|
||||
// If you build and start the app, the date returned here will have the same
|
||||
// value for all requests, as this method gets executed at build time.
|
||||
export function getStaticProps() {
|
||||
// Note that in this case we're returning the state directly, without creating
|
||||
// the store first (like in /pages/ssr.js), this approach can be better and easier
|
||||
return {
|
||||
props: {
|
||||
initialReduxState: {
|
||||
lastUpdate: Date.now(),
|
||||
light: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import Page from '../components/page'
|
||||
import { initializeStore } from '../store'
|
||||
|
||||
export default function SSR() {
|
||||
return <Page />
|
||||
}
|
||||
|
||||
// The date returned here will be different for every request that hits the page,
|
||||
// that is because the page becomes a serverless function instead of being statically
|
||||
// exported when you use `getServerSideProps` or `getInitialProps`
|
||||
export function getServerSideProps() {
|
||||
const reduxStore = initializeStore()
|
||||
const { dispatch } = reduxStore
|
||||
|
||||
dispatch({
|
||||
type: 'TICK',
|
||||
light: false,
|
||||
lastUpdate: Date.now(),
|
||||
})
|
||||
|
||||
return { props: { initialReduxState: reduxStore.getState() } }
|
||||
}
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -5,12 +5,10 @@ import type { AppProps } from 'next/app'
|
|||
|
||||
import store from '../app/store'
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Component {...pageProps} />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyApp
|
|
@ -1,74 +0,0 @@
|
|||
import { useMemo } from 'react'
|
||||
import { createStore, applyMiddleware } from 'redux'
|
||||
import { composeWithDevTools } from 'redux-devtools-extension'
|
||||
|
||||
let store
|
||||
|
||||
const initialState = {
|
||||
lastUpdate: 0,
|
||||
light: false,
|
||||
count: 0,
|
||||
}
|
||||
|
||||
const reducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'TICK':
|
||||
return {
|
||||
...state,
|
||||
lastUpdate: action.lastUpdate,
|
||||
light: !!action.light,
|
||||
}
|
||||
case 'INCREMENT':
|
||||
return {
|
||||
...state,
|
||||
count: state.count + 1,
|
||||
}
|
||||
case 'DECREMENT':
|
||||
return {
|
||||
...state,
|
||||
count: state.count - 1,
|
||||
}
|
||||
case 'RESET':
|
||||
return {
|
||||
...state,
|
||||
count: initialState.count,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function initStore(preloadedState = initialState) {
|
||||
return createStore(
|
||||
reducer,
|
||||
preloadedState,
|
||||
composeWithDevTools(applyMiddleware())
|
||||
)
|
||||
}
|
||||
|
||||
export const initializeStore = (preloadedState) => {
|
||||
let _store = store ?? initStore(preloadedState)
|
||||
|
||||
// After navigating to a page with an initial Redux state, merge that state
|
||||
// with the current state in the store, and create a new store
|
||||
if (preloadedState && store) {
|
||||
_store = initStore({
|
||||
...store.getState(),
|
||||
...preloadedState,
|
||||
})
|
||||
// Reset the current store
|
||||
store = undefined
|
||||
}
|
||||
|
||||
// For SSG and SSR always create a new store
|
||||
if (typeof window === 'undefined') return _store
|
||||
// Create the store once in the client
|
||||
if (!store) store = _store
|
||||
|
||||
return _store
|
||||
}
|
||||
|
||||
export function useStore(initialState) {
|
||||
const store = useMemo(() => initializeStore(initialState), [initialState])
|
||||
return store
|
||||
}
|
Loading…
Reference in a new issue