Skip to content

Commit

Permalink
Upgrade with-redux example to app router (#49994)
Browse files Browse the repository at this point in the history
### What?

Update **with-redux** example.

### Why?

**with-redux** example have:
- outdated packages
- outdated approaches and relies on **pages** directory

Since **app router** is stable now and is recommended to use, I've updated this example.

### How?

An update includes:
- move example to **app router**
- update **package.json** deps to the latest versions
- modernize jest by switching from **ts-node** to **@swc/jest**
- fix and overhaul **tests**
- modernize **redux** infra
- overhaul example **source code quality**


Co-authored-by: Balázs Orbán <18369201+balazsorban44@users.noreply.github.com>
  • Loading branch information
dvakatsiienko and balazsorban44 committed Jun 16, 2023
1 parent cbb69b2 commit 04cd1fd
Show file tree
Hide file tree
Showing 38 changed files with 478 additions and 489 deletions.
12 changes: 12 additions & 0 deletions examples/with-redux/app/api/identity-count/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* Core */
import { NextResponse } from 'next/server'

export async function POST(req: Request, res: Response) {
const body = await req.json()
const { amount = 1 } = body

// simulate IO latency
await new Promise((r) => setTimeout(r, 500))

return NextResponse.json({ data: amount })
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
'use client'

/* Core */
import { useState } from 'react'

import { useAppSelector, useAppDispatch } from '../../hooks'
/* Instruments */
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
counterSlice,
useSelector,
useDispatch,
selectCount,
} from './counterSlice'
import styles from './Counter.module.css'

function Counter() {
const dispatch = useAppDispatch()
const count = useAppSelector(selectCount)
const [incrementAmount, setIncrementAmount] = useState('2')
incrementAsync,
incrementIfOddAsync,
} from '@/lib/redux'
import styles from './counter.module.css'

const incrementValue = Number(incrementAmount) || 0
export const Counter = () => {
const dispatch = useDispatch()
const count = useSelector(selectCount)
const [incrementAmount, setIncrementAmount] = useState(2)

return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
onClick={() => dispatch(counterSlice.actions.decrement())}
>
-
</button>
<span className={styles.value}>{count}</span>
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
onClick={() => dispatch(counterSlice.actions.increment())}
>
+
</button>
Expand All @@ -42,29 +43,29 @@ function Counter() {
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
onChange={(e) => setIncrementAmount(Number(e.target.value ?? 0))}
/>
<button
className={styles.button}
onClick={() => dispatch(incrementByAmount(incrementValue))}
onClick={() =>
dispatch(counterSlice.actions.incrementByAmount(incrementAmount))
}
>
Add Amount
</button>
<button
className={styles.asyncButton}
onClick={() => dispatch(incrementAsync(incrementValue))}
onClick={() => dispatch(incrementAsync(incrementAmount))}
>
Add Async
</button>
<button
className={styles.button}
onClick={() => dispatch(incrementIfOdd(incrementValue))}
onClick={() => dispatch(incrementIfOddAsync(incrementAmount))}
>
Add If Odd
</button>
</div>
</div>
)
}

export default Counter
31 changes: 31 additions & 0 deletions examples/with-redux/app/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client'

/* Core */
import Link from 'next/link'
import { usePathname } from 'next/navigation'

/* Instruments */
import styles from '../styles/layout.module.css'

export const Nav = () => {
const pathname = usePathname()

return (
<nav className={styles.nav}>
<Link
className={`${styles.link} ${pathname === '/' ? styles.active : ''}`}
href="/"
>
Home
</Link>
<Link
className={`${styles.link} ${
pathname === '/verify' ? styles.active : ''
}`}
href="/verify"
>
Verify
</Link>
</nav>
)
}
File renamed without changes.
66 changes: 66 additions & 0 deletions examples/with-redux/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Components */
import { Providers } from '@/lib/providers'
import { Nav } from './components/Nav'

/* Instruments */
import styles from './styles/layout.module.css'
import './styles/globals.css'

export default function RootLayout(props: React.PropsWithChildren) {
return (
<Providers>
<html lang="en">
<body>
<section className={styles.container}>
<Nav />

<header className={styles.header}>
<img src="/logo.svg" className={styles.logo} alt="logo" />
</header>

<main className={styles.main}>{props.children}</main>

<footer className={styles.footer}>
<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>
</footer>
</section>
</body>
</html>
</Providers>
)
}
10 changes: 10 additions & 0 deletions examples/with-redux/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Components */
import { Counter } from './components/Counter/Counter'

export default function IndexPage() {
return <Counter />
}

export const metadata = {
title: 'Redux Toolkit',
}
16 changes: 16 additions & 0 deletions examples/with-redux/app/styles/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
html,
body {
min-height: 100vh;
padding: 0;
margin: 0;
font-family: system-ui, sans-serif;
}

a {
color: inherit;
text-decoration: none;
}

* {
box-sizing: border-box;
}
77 changes: 77 additions & 0 deletions examples/with-redux/app/styles/layout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
.container {
display: grid;
grid-template-areas:
'nav'
'header'
'main'
'footer';
grid-template-rows: auto auto 1fr 36px;
align-items: center;
min-height: 100vh;
}

.logo {
height: 40vmin;
pointer-events: none;
}

.header {
grid-area: header;
}

.main {
grid-area: main;
}

.header,
.main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.footer {
grid-area: footer;
justify-self: center;
}

.nav {
grid-area: nav;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 8px;
font-size: calc(10px + 2vmin);
}

.link:hover {
text-decoration: underline;
}

.link {
color: #704cb6;
}

.link.active {
text-decoration: underline;
}

@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);
}
}
11 changes: 11 additions & 0 deletions examples/with-redux/app/verify/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function VerifyPage() {
return (
<>
<h1>Verify page</h1>
<p>
This page is intended to verify that Redux state is persisted across
page navigations.
</p>
</>
)
}
17 changes: 0 additions & 17 deletions examples/with-redux/jest.config.ts

This file was deleted.

11 changes: 11 additions & 0 deletions examples/with-redux/lib/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'

/* Core */
import { Provider } from 'react-redux'

/* Instruments */
import { reduxStore } from '@/lib/redux'

export const Providers = (props: React.PropsWithChildren) => {
return <Provider store={reduxStore}>{props.children}</Provider>
}
14 changes: 14 additions & 0 deletions examples/with-redux/lib/redux/createAppAsyncThunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* Core */
import { createAsyncThunk } from '@reduxjs/toolkit'

/* Instruments */
import type { ReduxState, ReduxDispatch } from './store'

/**
* ? A utility function to create a typed Async Thnuk Actions.
*/
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: ReduxState
dispatch: ReduxDispatch
rejectValue: string
}>()
2 changes: 2 additions & 0 deletions examples/with-redux/lib/redux/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './store'
export * from './slices'
20 changes: 20 additions & 0 deletions examples/with-redux/lib/redux/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* Core */
import { createLogger } from 'redux-logger'

const middleware = [
createLogger({
duration: true,
timestamp: false,
collapsed: true,
colors: {
title: () => '#139BFE',
prevState: () => '#1C5FAF',
action: () => '#149945',
nextState: () => '#A47104',
error: () => '#ff0005',
},
predicate: () => typeof window !== 'undefined',
}),
]

export { middleware }
6 changes: 6 additions & 0 deletions examples/with-redux/lib/redux/rootReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Instruments */
import { counterSlice } from './slices'

export const reducer = {
counter: counterSlice.reducer,
}

0 comments on commit 04cd1fd

Please sign in to comment.