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:
Lee Robinson 2021-09-15 10:34:31 -05:00 committed by GitHub
parent 6a9cda5726
commit dd4215b81e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 27 additions and 971 deletions

View file

@ -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

View file

@ -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)).

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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"
}
}

View file

@ -1,4 +0,0 @@
import { reduxPage } from '../config/redux'
import About from '../containers/about'
export default reduxPage(About)

View file

@ -1,4 +0,0 @@
import { reduxPage } from '../config/redux'
import Homepage from '../containers/homepage'
export default reduxPage(Homepage)

View file

@ -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

View file

@ -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).

View file

@ -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"
}
}

View file

@ -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

View file

@ -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).

View file

@ -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

View file

@ -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

View file

@ -1,8 +0,0 @@
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer,
},
})

View file

@ -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>
)
}

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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>
)
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 />
</>
)
}

View file

@ -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',

View file

@ -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

View file

@ -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"
}
}

View file

@ -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>
)
}

View file

@ -1,5 +0,0 @@
import Page from '../components/page'
export default function Index() {
return <Page />
}

View file

@ -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,
},
},
}
}

View file

@ -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() } }
}

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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

View file

@ -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
}