Skip to content

Commit 88eaa3f

Browse files
committed
feat: add nextjs api & react hook form example
1 parent aebc9da commit 88eaa3f

File tree

10 files changed

+199
-11
lines changed

10 files changed

+199
-11
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"dependencies": {
2121
"@emotion/react": "^11.11.1",
2222
"@emotion/styled": "^11.11.0",
23+
"@hookform/resolvers": "^3.3.1",
2324
"@mui/icons-material": "^5.14.9",
2425
"@mui/material": "^5.14.10",
2526
"dayjs": "^1.11.10",

src/app/api/hello/route.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/app/api/test/route.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export const GET = async (req: Request) => {
4+
const { searchParams } = new URL(req.url);
5+
const reqData = Object.fromEntries(searchParams);
6+
return NextResponse.json({
7+
message: 'Test getApiResponse GET success!',
8+
method: 'GET',
9+
reqData,
10+
});
11+
};
12+
13+
export const POST = async (req: Request) => {
14+
const reqData = await req.json();
15+
return NextResponse.json({
16+
message: 'Test postApiResponse POST success!',
17+
method: 'POST',
18+
reqData,
19+
});
20+
};

src/app/layout.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { Container } from '@mui/material';
2+
import GlobalStyles from '@mui/material/GlobalStyles';
13
import { Metadata } from 'next';
24
import * as React from 'react';
35

4-
import { SITE_CONFIG } from '@/constant/config';
6+
import { GLOBAL_STYLES, SITE_CONFIG } from '@/constant';
57

68
// !STARTERCONF Change these default meta
79
// !STARTERCONF Look at @/constant/config to change them
@@ -50,8 +52,11 @@ export default function RootLayout({
5052
children: React.ReactNode;
5153
}) {
5254
return (
53-
<html>
54-
<body>{children}</body>
55+
<html lang='en'>
56+
<GlobalStyles styles={GLOBAL_STYLES} />
57+
<body>
58+
<Container sx={{ pl: 0, pr: 0 }}>{children}</Container>
59+
</body>
5560
</html>
5661
);
5762
}

src/component/Homepage.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Box, Typography } from '@mui/material';
33
import Link from 'next/link';
44

55
import PageFooter from '@/component/shared/PageFooter';
6+
import ReactHookForm from '@/component/shared/ReactHookForm';
67
import { SITE_CONFIG } from '@/constant';
78

89
export default function Homepage({ reactVersion = 'unknown' }) {
@@ -11,10 +12,19 @@ export default function Homepage({ reactVersion = 'unknown' }) {
1112
<section>
1213
<Box sx={{ textAlign: 'center' }}>
1314
<PinDropIcon />
14-
<Typography variant='h5' component='h1' gutterBottom>
15+
<Typography
16+
variant='h5'
17+
component='h1'
18+
gutterBottom
19+
className='page-title'
20+
>
1521
{SITE_CONFIG.title}
1622
</Typography>
17-
<Typography variant='subtitle2' gutterBottom>
23+
<Typography
24+
variant='subtitle2'
25+
gutterBottom
26+
className='page-subtitle'
27+
>
1828
{SITE_CONFIG.description}
1929
</Typography>
2030

@@ -42,6 +52,20 @@ export default function Homepage({ reactVersion = 'unknown' }) {
4252
Click here to deploy a demo site to your Vercel in 1 minute
4353
</Link>
4454
</Box>
55+
<Box sx={{ m: 5 }}>
56+
<Link
57+
href='/api/test?from=github&nextjs=yes&mui=yes&tailwind=no'
58+
target='_blank'
59+
>
60+
Test NextJs API method GET with parameters
61+
</Link>
62+
</Box>
63+
64+
<Box sx={{ m: 5 }}>
65+
<h4>Test NextJs API method POST with parameters</h4>
66+
<ReactHookForm />
67+
</Box>
68+
4569
<Box sx={{ m: 5 }}>
4670
<Link href='/test-page-not-exists'>Test 404 page not found</Link>
4771
</Box>

src/component/shared/PageFooter.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import { Box } from '@mui/material';
22
import * as React from 'react';
33

4+
import ServerDateTime from '@/component/shared/ServerDateTime';
5+
46
const PageFooter = () => {
57
return (
68
<section>
79
<Box component='footer' sx={{ m: 5, textAlign: 'center' }}>
8-
PageFooter.tsx © {new Date().getFullYear()} Boilerplate live example:
10+
PageFooter.tsx © Boilerplate live example:
911
<a href='https://hihb.com' target='_blank'>
1012
HiHB
1113
</a>
1214
</Box>
15+
<Box sx={{ m: 2, textAlign: 'center', fontSize: '0.8rem' }}>
16+
<ServerDateTime cityTimezone='America/New_York' />
17+
</Box>
1318
</section>
1419
);
1520
};
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use client';
2+
3+
import { zodResolver } from '@hookform/resolvers/zod';
4+
import { Box, Button, FormHelperText } from '@mui/material';
5+
import React from 'react';
6+
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
7+
import { z } from 'zod';
8+
9+
import { consoleLog } from '@/util/shared/console-log';
10+
import { getApiResponse } from '@/util/shared/get-api-response';
11+
12+
const zodSchema = z.object({
13+
name: z.string().min(5).nonempty({ message: 'Name is required' }),
14+
email: z.string().min(10).email({ message: 'Invalid email address' }),
15+
});
16+
17+
type FormValues = z.infer<typeof zodSchema>;
18+
19+
const ReactHookForm: React.FC = () => {
20+
const apiEndpoint = '/api/test';
21+
const [apiResult, setApiResult] = React.useState<FormValues>();
22+
23+
const {
24+
handleSubmit,
25+
control,
26+
formState: { errors },
27+
} = useForm<FormValues>({
28+
resolver: zodResolver(zodSchema),
29+
});
30+
31+
const onSubmit: SubmitHandler<FormValues> = async (data) => {
32+
try {
33+
const result = await getApiResponse<{
34+
reqData: FormValues;
35+
}>({
36+
apiEndpoint,
37+
method: 'POST',
38+
requestData: JSON.stringify(data),
39+
});
40+
setApiResult(result?.reqData);
41+
consoleLog('getApiResponse result', result, errors);
42+
} catch (error) {
43+
consoleLog('handleSubmit ERROR', error);
44+
}
45+
};
46+
47+
return (
48+
<form onSubmit={handleSubmit(onSubmit)}>
49+
<Box sx={{ m: 2 }}>
50+
<label>Name:</label>
51+
<Controller
52+
name='name'
53+
control={control}
54+
defaultValue=''
55+
render={({ field }) => <input {...field} />}
56+
/>
57+
{errors.name && (
58+
<FormHelperText sx={{ textAlign: 'center' }}>
59+
{errors.name.message}
60+
</FormHelperText>
61+
)}
62+
</Box>
63+
64+
<Box sx={{ m: 2 }}>
65+
<label>Email:</label>
66+
<Controller
67+
name='email'
68+
control={control}
69+
defaultValue=''
70+
render={({ field }) => <input {...field} />}
71+
/>
72+
{errors.email && (
73+
<FormHelperText sx={{ textAlign: 'center' }}>
74+
{errors.email.message}
75+
</FormHelperText>
76+
)}
77+
</Box>
78+
{apiResult && (
79+
<Box sx={{ m: 2, color: 'green' }}>
80+
API result from {apiEndpoint}: {apiResult.name} & {apiResult.email}
81+
</Box>
82+
)}
83+
<Button variant='contained' type='submit'>
84+
Test react hook form with zod
85+
</Button>
86+
</form>
87+
);
88+
};
89+
90+
export default ReactHookForm;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import dayjs from 'dayjs';
2+
import timezone from 'dayjs/plugin/timezone';
3+
import utc from 'dayjs/plugin/utc';
4+
5+
dayjs.extend(utc);
6+
dayjs.extend(timezone);
7+
8+
const ServerDateTime = ({
9+
cityTimezone,
10+
timeFormat = 'dddd, MMMM D, YYYY h:mm:ss A',
11+
color,
12+
date,
13+
}: {
14+
cityTimezone: string;
15+
timeFormat?: string;
16+
color?: string;
17+
date?: string;
18+
}) => {
19+
return (
20+
<span className='date-time' style={{ color }}>
21+
{dayjs(date).tz(cityTimezone).format(timeFormat)}
22+
</span>
23+
);
24+
};
25+
26+
export default ServerDateTime;

src/constant/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { blue, grey } from '@mui/material/colors';
2+
13
export const SITE_CONFIG = {
24
title: 'NextJs 13.x + MUI 5.x + TypeScript Starter',
35
description:
@@ -10,3 +12,18 @@ export const HIDE_DEBUG_ARY = [
1012
// 'getApiResponse',
1113
'getMongoDbApiData',
1214
];
15+
16+
export const GLOBAL_STYLES = {
17+
body: { margin: 4 },
18+
'.page-title': { color: 'darkblue' },
19+
'.page-subtitle': { color: grey[600] },
20+
a: {
21+
textDecoration: 'underline',
22+
textDecorationColor: blue[800],
23+
color: blue['700'],
24+
fontSize: '1rem',
25+
fontWeight: 400,
26+
lineHeight: '1.8',
27+
letterSpacing: '0.00938em',
28+
},
29+
};

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,11 @@
13731373
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d"
13741374
integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==
13751375

1376+
"@hookform/resolvers@^3.3.1":
1377+
version "3.3.1"
1378+
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.1.tgz#b7cbfe767434f52cba6b99b0a9a0b73eb8895188"
1379+
integrity sha512-K7KCKRKjymxIB90nHDQ7b9nli474ru99ZbqxiqDAWYsYhOsU3/4qLxW91y+1n04ic13ajjZ66L3aXbNef8PELQ==
1380+
13761381
"@humanwhocodes/config-array@^0.11.11":
13771382
version "0.11.11"
13781383
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"

0 commit comments

Comments
 (0)