Skip to content

Commit 2745777

Browse files
committed
Initial commit
Created from https://vercel.com/new
0 parents  commit 2745777

26 files changed

+841
-0
lines changed

.gitignore

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Redux Toolkit TypeScript Example
2+
3+
This example shows how to integrate Next.js with [Redux Toolkit](https://redux-toolkit.js.org).
4+
5+
**Redux Toolkit**(also known as "RTK" for short) provides a standardized way to write Redux logic. It includes utilities that help simplify many common use cases, including [store setup](https://redux-toolkit.js.org/api/configureStore), [creating reducers and writing immutable update logic](https://redux-toolkit.js.org/api/createreducer), and even [creating entire "slices" of state at once](https://redux-toolkit.js.org/api/createslice). This example showcases each of these features in conjunction with Next.js.
6+
7+
## Deploy Your Own
8+
9+
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
10+
11+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-redux&project-name=with-redux&repository-name=with-redux)
12+
13+
## How to Use
14+
15+
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
16+
17+
```bash
18+
npx create-next-app --example with-redux with-redux-app
19+
```
20+
21+
```bash
22+
yarn create next-app --example with-redux with-redux-app
23+
```
24+
25+
```bash
26+
pnpm create next-app --example with-redux with-redux-app
27+
```
28+
29+
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)).

app/StoreProvider.tsx

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client";
2+
import type { AppStore } from "@/lib/store";
3+
import { makeStore } from "@/lib/store";
4+
import { setupListeners } from "@reduxjs/toolkit/query";
5+
import type { ReactNode } from "react";
6+
import { useEffect, useRef } from "react";
7+
import { Provider } from "react-redux";
8+
9+
interface Props {
10+
readonly children: ReactNode;
11+
}
12+
13+
export const StoreProvider = ({ children }: Props) => {
14+
const storeRef = useRef<AppStore | null>(null);
15+
16+
if (!storeRef.current) {
17+
// Create the store instance the first time this renders
18+
storeRef.current = makeStore();
19+
}
20+
21+
useEffect(() => {
22+
if (storeRef.current != null) {
23+
// configure listeners using the provided defaults
24+
// optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
25+
const unsubscribe = setupListeners(storeRef.current.dispatch);
26+
return unsubscribe;
27+
}
28+
}, []);
29+
30+
return <Provider store={storeRef.current}>{children}</Provider>;
31+
};

app/api/counter/route.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { NextRequest } from "next/server";
2+
import { NextResponse } from "next/server";
3+
4+
interface Context {
5+
params: undefined;
6+
}
7+
8+
export async function POST(request: NextRequest, context: Context) {
9+
const body: { amount: number } = await request.json();
10+
const { amount = 1 } = body;
11+
12+
// simulate IO latency
13+
await new Promise((resolve) => setTimeout(resolve, 500));
14+
15+
return NextResponse.json({ data: amount });
16+
}

app/components/Nav.tsx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
3+
import Link from "next/link";
4+
import { usePathname } from "next/navigation";
5+
6+
import styles from "../styles/layout.module.css";
7+
8+
export const Nav = () => {
9+
const pathname = usePathname();
10+
11+
return (
12+
<nav className={styles.nav}>
13+
<Link
14+
className={`${styles.link} ${pathname === "/" ? styles.active : ""}`}
15+
href="/"
16+
>
17+
Home
18+
</Link>
19+
<Link
20+
className={`${styles.link} ${
21+
pathname === "/verify" ? styles.active : ""
22+
}`}
23+
href="/verify"
24+
>
25+
Verify
26+
</Link>
27+
<Link
28+
className={`${styles.link} ${
29+
pathname === "/quotes" ? styles.active : ""
30+
}`}
31+
href="/quotes"
32+
>
33+
Quotes
34+
</Link>
35+
</nav>
36+
);
37+
};
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
.row {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
}
6+
7+
.row > button {
8+
margin-left: 4px;
9+
margin-right: 8px;
10+
}
11+
12+
.row:not(:last-child) {
13+
margin-bottom: 16px;
14+
}
15+
16+
.value {
17+
font-size: 78px;
18+
padding-left: 16px;
19+
padding-right: 16px;
20+
margin-top: 2px;
21+
font-family: "Courier New", Courier, monospace;
22+
}
23+
24+
.button {
25+
appearance: none;
26+
background: none;
27+
font-size: 32px;
28+
padding-left: 12px;
29+
padding-right: 12px;
30+
outline: none;
31+
border: 2px solid transparent;
32+
color: rgb(112, 76, 182);
33+
padding-bottom: 4px;
34+
cursor: pointer;
35+
background-color: rgba(112, 76, 182, 0.1);
36+
border-radius: 2px;
37+
transition: all 0.15s;
38+
}
39+
40+
.textbox {
41+
font-size: 32px;
42+
padding: 2px;
43+
width: 64px;
44+
text-align: center;
45+
margin-right: 4px;
46+
}
47+
48+
.button:hover,
49+
.button:focus {
50+
border: 2px solid rgba(112, 76, 182, 0.4);
51+
}
52+
53+
.button:active {
54+
background-color: rgba(112, 76, 182, 0.2);
55+
}
56+
57+
.asyncButton {
58+
composes: button;
59+
position: relative;
60+
}
61+
62+
.asyncButton:after {
63+
content: "";
64+
background-color: rgba(112, 76, 182, 0.15);
65+
display: block;
66+
position: absolute;
67+
width: 100%;
68+
height: 100%;
69+
left: 0;
70+
top: 0;
71+
opacity: 0;
72+
transition:
73+
width 1s linear,
74+
opacity 0.5s ease 1s;
75+
}
76+
77+
.asyncButton:active:after {
78+
width: 0%;
79+
opacity: 1;
80+
transition: 0s;
81+
}

app/components/counter/Counter.tsx

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
5+
import {
6+
decrement,
7+
increment,
8+
incrementAsync,
9+
incrementByAmount,
10+
incrementIfOdd,
11+
selectCount,
12+
selectStatus,
13+
} from "@/lib/features/counter/counterSlice";
14+
15+
import { useAppDispatch, useAppSelector } from "@/lib/hooks";
16+
import styles from "./Counter.module.css";
17+
18+
export const Counter = () => {
19+
const dispatch = useAppDispatch();
20+
const count = useAppSelector(selectCount);
21+
const status = useAppSelector(selectStatus);
22+
const [incrementAmount, setIncrementAmount] = useState("2");
23+
24+
const incrementValue = Number(incrementAmount) || 0;
25+
26+
return (
27+
<div>
28+
<div className={styles.row}>
29+
<button
30+
className={styles.button}
31+
aria-label="Decrement value"
32+
onClick={() => dispatch(decrement())}
33+
>
34+
-
35+
</button>
36+
<span aria-label="Count" className={styles.value}>
37+
{count}
38+
</span>
39+
<button
40+
className={styles.button}
41+
aria-label="Increment value"
42+
onClick={() => dispatch(increment())}
43+
>
44+
+
45+
</button>
46+
</div>
47+
<div className={styles.row}>
48+
<input
49+
className={styles.textbox}
50+
aria-label="Set increment amount"
51+
value={incrementAmount}
52+
type="number"
53+
onChange={(e) => {
54+
setIncrementAmount(e.target.value);
55+
}}
56+
/>
57+
<button
58+
className={styles.button}
59+
onClick={() => dispatch(incrementByAmount(incrementValue))}
60+
>
61+
Add Amount
62+
</button>
63+
<button
64+
className={styles.asyncButton}
65+
disabled={status !== "idle"}
66+
onClick={() => dispatch(incrementAsync(incrementValue))}
67+
>
68+
Add Async
69+
</button>
70+
<button
71+
className={styles.button}
72+
onClick={() => {
73+
dispatch(incrementIfOdd(incrementValue));
74+
}}
75+
>
76+
Add If Odd
77+
</button>
78+
</div>
79+
</div>
80+
);
81+
};
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.select {
2+
font-size: 25px;
3+
padding: 5px;
4+
padding-top: 2px;
5+
padding-bottom: 2px;
6+
size: 50;
7+
outline: none;
8+
border: 2px solid transparent;
9+
color: rgb(112, 76, 182);
10+
cursor: pointer;
11+
background-color: rgba(112, 76, 182, 0.1);
12+
border-radius: 5px;
13+
transition: all 0.15s;
14+
}
15+
16+
.container {
17+
display: flex;
18+
flex-direction: column;
19+
align-items: center;
20+
}

app/components/quotes/Quotes.tsx

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"use client";
2+
import { useGetQuotesQuery } from "@/lib/features/quotes/quotesApiSlice";
3+
import { useState } from "react";
4+
import styles from "./Quotes.module.css";
5+
6+
const options = [5, 10, 20, 30];
7+
8+
export const Quotes = () => {
9+
const [numberOfQuotes, setNumberOfQuotes] = useState(10);
10+
// Using a query hook automatically fetches data and returns query values
11+
const { data, isError, isLoading, isSuccess } =
12+
useGetQuotesQuery(numberOfQuotes);
13+
14+
if (isError) {
15+
return (
16+
<div>
17+
<h1>There was an error!!!</h1>
18+
</div>
19+
);
20+
}
21+
22+
if (isLoading) {
23+
return (
24+
<div>
25+
<h1>Loading...</h1>
26+
</div>
27+
);
28+
}
29+
30+
if (isSuccess) {
31+
return (
32+
<div className={styles.container}>
33+
<h3>Select the Quantity of Quotes to Fetch:</h3>
34+
<select
35+
className={styles.select}
36+
value={numberOfQuotes}
37+
onChange={(e) => {
38+
setNumberOfQuotes(Number(e.target.value));
39+
}}
40+
>
41+
{options.map((option) => (
42+
<option key={option} value={option}>
43+
{option}
44+
</option>
45+
))}
46+
</select>
47+
{data.quotes.map(({ author, quote, id }) => (
48+
<blockquote key={id}>
49+
&ldquo;{quote}&rdquo;
50+
<footer>
51+
<cite>{author}</cite>
52+
</footer>
53+
</blockquote>
54+
))}
55+
</div>
56+
);
57+
}
58+
59+
return null;
60+
};

app/icon.ico

3.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)