Skip to content

Commit 6d0dd94

Browse files
authored
Merge pull request #10 from AlexStack/react19-nextjs15
NextJs15 with React19
2 parents 7d8da1a + 1772eef commit 6d0dd94

File tree

9 files changed

+563
-173
lines changed

9 files changed

+563
-173
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# NextJs 14.x + MUI 5.x + React Hook Form + TypeScript Starter and Boilerplate
1+
# ReactJs 19.x + NextJs 15.x + MUI 6.x + TypeScript Starter and Boilerplate
22

33
<div align="center">
4-
<h2>2024/2025: 🔋 NextJs 14.x + MUI 5.x + TypeScript Starter</h2>
5-
<p>The scaffold for NextJs 14.x (App Router), React Hook Form, Material UI(MUI 5.x),Typescript and ESLint, and TypeScript with Absolute Import, Seo, Link component, pre-configured with Husky.</p>
4+
<h2>2024/2025: 🔋 ReactJs 19.x + NextJs 15.x + MUI 6.x + TypeScript Starter</h2>
5+
<p>The scaffold for NextJs 15.x (App Router), React Hook Form, Material UI(MUI 6.x),Typescript and ESLint, and TypeScript with Absolute Import, Seo, Link component, pre-configured with Husky.</p>
66

7-
<p>With simple example of NextJs API, React-hook-form with zod, fetch remote api, 404/500 error pages, MUI SSR usage, Styled component, MUI AlertBar, MUI confirmation dialog, Loading button, Client-side component & React Context update hook</p>
7+
<p>With simple example of ReactJs 19.x, NextJs 15.x API, React-hook-form with zod, fetch remote api, 404/500 error pages, MUI SSR usage, Styled component, MUI AlertBar, MUI confirmation dialog, Loading button, Client-side component & React Context update hook</p>
88

99
🚘🚘🚘 [**Click here to see an online demo**](https://mui-nextjs-ts.vercel.app) 🚘🚘🚘
1010

@@ -18,12 +18,19 @@ If you prefer Tailwind css, check this: [Tailwind-CSS-Version](https://github.co
1818

1919
🚘🚘🚘 [**Click here to see an online demo**](https://mui-nextjs-ts.vercel.app) 🚘🚘🚘
2020

21+
## Clone this repository for React 19.x with NextJs 15.x or React 18.x with NextJs 14.x
22+
23+
- Clone React19-Next15-MUI6-TS-Starter:
24+
- `git clone -b react19-nextjs15 https://github.com/AlexStack/nextjs-materia-mui-typescript-hook-form-scaffold-boilerplate-starter.git react19-nextjs15-mui6-ts-starter`
25+
- Clone React18-Next14-MUI5-TS-Starter:
26+
- `git clone -b nextjs14 https://github.com/AlexStack/nextjs-materia-mui-typescript-hook-form-scaffold-boilerplate-starter.git react18-nextjs14-mui5-ts-starter`
27+
2128
## Features
2229

2330
This repository is 🔋 battery packed with:
2431

25-
- ⚡️ Next.js 14.x with App Router
26-
- ⚛️ React 18.x
32+
- ⚡️ Next.js 15.x with App Router
33+
- ⚛️ React 19.x
2734
- ✨ TypeScript
2835
- 💨 Material UI — Ready to use Material Design components [check here for the usage](https://mui.com/material-ui/getting-started/usage/)
2936
- 🎨 React Hook Form — Performant, flexible and extensible forms with easy-to-use validation

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "nextjs-materia-mui-typescript-hook-form-scaffold-boilerplate-starter",
3-
"version": "0.1.0",
2+
"name": "react19-nextjs15-materia-mui6-typescript-hook-form-scaffold-boilerplate-starter",
3+
"version": "2.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
@@ -24,9 +24,9 @@
2424
"@mui/icons-material": "^5.14.9",
2525
"@mui/material": "^5.14.10",
2626
"dayjs": "^1.11.10",
27-
"next": "^14.0.1",
28-
"react": "^18.2.0",
29-
"react-dom": "^18.2.0",
27+
"next": "^15.0.0-rc.0",
28+
"react": "^19.0.0-rc-512b09b2-20240718",
29+
"react-dom": "^19.0.0-rc-512b09b2-20240718",
3030
"react-hook-form": "^7.46.2",
3131
"react-icons": "^4.10.1",
3232
"zod": "^3.22.4"
@@ -42,7 +42,7 @@
4242
"@typescript-eslint/parser": "^5.62.0",
4343
"autoprefixer": "^10.4.14",
4444
"eslint": "^8.45.0",
45-
"eslint-config-next": "^14.0.1",
45+
"eslint-config-next": "^15.0.0-rc.0",
4646
"eslint-config-prettier": "^8.8.0",
4747
"eslint-plugin-simple-import-sort": "^7.0.0",
4848
"eslint-plugin-unused-imports": "^2.0.0",

src/app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ const loadDataFromApi = async (slug?: string) => {
1212
// Fetch & cache data from 2 remote APIs test
1313
const [reactNpmData, nextJsNpmData] = await Promise.all([
1414
getApiResponse<NpmData>({
15-
apiEndpoint: 'https://registry.npmjs.org/react/latest',
15+
apiEndpoint: 'https://registry.npmjs.org/react/rc',
1616
revalidate: 60 * 60 * 24, // 24 hours cache
1717
timeout: 5000, // 5 seconds
1818
}),
1919
getApiResponse<NpmData>({
20-
apiEndpoint: 'https://registry.npmjs.org/next/latest',
20+
apiEndpoint: 'https://registry.npmjs.org/next/rc',
2121
revalidate: 0, // no cache
2222
timeout: 5000, // 5 seconds
2323
}),

src/components/Homepage.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import PinDropIcon from '@mui/icons-material/PinDrop';
1+
import AutoAwesome from '@mui/icons-material/AutoAwesome';
22
import { Box, Typography } from '@mui/material';
33
import Link from 'next/link';
44

55
import { ClientProvider } from '@/hooks/useClientContext';
66

77
import DisplayRandomPicture from '@/components/shared/DisplayRandomPicture';
88
import PageFooter from '@/components/shared/PageFooter';
9+
import ReactActionForm from '@/components/shared/ReactActionForm';
910
import ReactHookForm from '@/components/shared/ReactHookForm';
1011

1112
import { FETCH_API_CTX_VALUE, SITE_CONFIG } from '@/constants';
@@ -18,7 +19,7 @@ export default function Homepage({
1819
<main>
1920
<section>
2021
<Box sx={{ textAlign: 'center' }}>
21-
<PinDropIcon
22+
<AutoAwesome
2223
className='page-title'
2324
sx={{ width: '3rem', height: '3rem' }}
2425
/>
@@ -44,8 +45,8 @@ export default function Homepage({
4445
sx={{ color: 'green', mt: 3 }}
4546
>
4647
Fetch & cache data from 2 remote APIs test: <br />
47-
The latest React version is {reactVersion}, and the latest NextJs
48-
version is {nextJsVersion}
48+
The React RC version is {reactVersion}, and the NextJs RC version is{' '}
49+
{nextJsVersion}
4950
<Box sx={{ color: 'grey', fontSize: 10 }}>
5051
On dev environment, you can see how long it takes on console, e.g.
5152
getApiResponse: 0.05s
@@ -67,6 +68,7 @@ export default function Homepage({
6768
component)
6869
</h4>
6970
<ClientProvider defaultValue={FETCH_API_CTX_VALUE}>
71+
<ReactActionForm />
7072
<ReactHookForm />
7173
<DisplayRandomPicture />
7274
</ClientProvider>

src/components/shared/DisplayRandomPicture.tsx

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Avatar from '@mui/material/Avatar';
88
import Button from '@mui/material/Button';
99
import { purple } from '@mui/material/colors';
1010
import Stack from '@mui/material/Stack';
11-
import React, { useEffect, useState } from 'react';
11+
import React, { useEffect, useState, useTransition } from 'react';
1212

1313
import { useAlertBar } from '@/hooks/useAlertBar';
1414
import { useClientContext } from '@/hooks/useClientContext';
@@ -20,56 +20,56 @@ import { getApiResponse } from '@/utils/shared/get-api-response';
2020

2121
const DisplayRandomPicture = () => {
2222
const [imageUrl, setImageUrl] = useState('');
23-
const [loading, setLoading] = useState(false);
2423
const [error, setError] = useState('');
2524
const { fetchCount, updateClientCtx } = useClientContext<FetchApiContext>();
2625
const { setAlertBarProps, renderAlertBar } = useAlertBar();
2726
const renderCountRef = React.useRef(0);
27+
const [isPending, startTransition] = useTransition();
2828

2929
const fetchRandomPicture = async () => {
30-
if (loading) {
31-
setAlertBarProps({
32-
message: 'Please wait for the current fetch to complete',
33-
severity: 'warning',
34-
});
35-
return;
36-
}
37-
setLoading(true);
38-
setError('');
30+
startTransition(async () => {
31+
if (isPending) {
32+
setAlertBarProps({
33+
message: 'Please wait for the current fetch to complete',
34+
severity: 'warning',
35+
});
36+
return;
37+
}
38+
setError('');
3939

40-
try {
41-
const response = await getApiResponse<Response & { url: string }>({
42-
apiEndpoint: 'https://picsum.photos/300/160',
43-
timeout: 5001,
44-
});
40+
try {
41+
const response = await getApiResponse<Response & { url: string }>({
42+
apiEndpoint: 'https://picsum.photos/300/160',
43+
timeout: 5001,
44+
});
4545

46-
if (!response?.url) {
47-
throw new Error('Error fetching the image, no response url');
48-
}
46+
if (!response?.url) {
47+
throw new Error('Error fetching the image, no response url');
48+
}
4949

50-
setImageUrl(response.url);
51-
updateClientCtx({ fetchCount: fetchCount + 1 });
52-
setAlertBarProps({
53-
message: 'A random picture fetched successfully',
54-
severity: 'info',
55-
});
56-
} catch (error) {
57-
const errorMsg =
58-
error instanceof Error ? error.message : 'Error fetching the image';
50+
setImageUrl(response.url);
51+
updateClientCtx({ fetchCount: fetchCount + 1 });
52+
setAlertBarProps({
53+
message: 'A random picture fetched successfully',
54+
severity: 'info',
55+
});
56+
} catch (error) {
57+
const errorMsg =
58+
error instanceof Error ? error.message : 'Error fetching the image';
5959

60-
setError(errorMsg);
61-
setAlertBarProps({
62-
message: errorMsg,
63-
severity: 'error',
64-
});
65-
setLoading(false);
66-
} finally {
67-
setLoading(false);
68-
}
60+
setError(errorMsg);
61+
setAlertBarProps({
62+
message: errorMsg,
63+
severity: 'error',
64+
});
65+
} finally {
66+
// setLoading(false);
67+
}
68+
});
6969
};
7070

7171
useEffect(() => {
72-
if (renderCountRef.current === 0 && !loading) {
72+
if (renderCountRef.current === 0 && !isPending) {
7373
fetchRandomPicture();
7474
}
7575
renderCountRef.current += 1;
@@ -83,7 +83,6 @@ const DisplayRandomPicture = () => {
8383
spacing={2}
8484
sx={{ position: 'relative', width: '300px', margin: '0 auto' }}
8585
>
86-
{error && <p>{error}</p>}
8786
{imageUrl && (
8887
<Avatar
8988
alt='DisplayRandomPicture'
@@ -92,27 +91,31 @@ const DisplayRandomPicture = () => {
9291
sx={{ width: 300, height: 150, borderRadius: '10px' }}
9392
/>
9493
)}
94+
{error && <p>{error}</p>}
9595
<div>
96-
{loading ? <span>Loading...</span> : null} Component Render Count:{' '}
96+
{isPending && <span>Loading...</span>} Component Render Count:{' '}
9797
{renderCountRef.current + 1}
9898
</div>
9999

100100
<SubmitButton
101-
isSubmitting={loading}
101+
isSubmitting={isPending}
102102
submittingText='Fetching Picture ...'
103103
>
104104
<Button
105105
variant='contained'
106106
endIcon={<Send />}
107107
onClick={fetchRandomPicture}
108-
disabled={loading}
108+
disabled={isPending}
109109
color='secondary'
110110
>
111111
Get Another Picture
112112
</Button>
113113
</SubmitButton>
114114
{imageUrl && (
115-
<StyledRefreshButton onClick={fetchRandomPicture} loading={loading}>
115+
<StyledRefreshButton
116+
onClick={fetchRandomPicture}
117+
loading={isPending ? 1 : 0}
118+
>
116119
<Avatar sx={{ width: 24, height: 24 }}>
117120
<Autorenew />
118121
</Avatar>
@@ -131,7 +134,7 @@ const spin = keyframes`
131134
transform: rotate(360deg);
132135
}
133136
`;
134-
const StyledRefreshButton = styled.div<{ loading?: boolean }>`
137+
const StyledRefreshButton = styled.div<{ loading: number }>`
135138
position: absolute;
136139
right: 0;
137140
top: 0;

0 commit comments

Comments
 (0)