Skip to content

Commit

Permalink
Merge pull request #10 from AlexStack/react19-nextjs15
Browse files Browse the repository at this point in the history
NextJs15 with React19
  • Loading branch information
AlexStack authored Aug 9, 2024
2 parents 7d8da1a + 1772eef commit 6d0dd94
Show file tree
Hide file tree
Showing 9 changed files with 563 additions and 173 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# NextJs 14.x + MUI 5.x + React Hook Form + TypeScript Starter and Boilerplate
# ReactJs 19.x + NextJs 15.x + MUI 6.x + TypeScript Starter and Boilerplate

<div align="center">
<h2>2024/2025: 🔋 NextJs 14.x + MUI 5.x + TypeScript Starter</h2>
<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>
<h2>2024/2025: 🔋 ReactJs 19.x + NextJs 15.x + MUI 6.x + TypeScript Starter</h2>
<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>

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

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

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

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

## Clone this repository for React 19.x with NextJs 15.x or React 18.x with NextJs 14.x

- Clone React19-Next15-MUI6-TS-Starter:
- `git clone -b react19-nextjs15 https://github.com/AlexStack/nextjs-materia-mui-typescript-hook-form-scaffold-boilerplate-starter.git react19-nextjs15-mui6-ts-starter`
- Clone React18-Next14-MUI5-TS-Starter:
- `git clone -b nextjs14 https://github.com/AlexStack/nextjs-materia-mui-typescript-hook-form-scaffold-boilerplate-starter.git react18-nextjs14-mui5-ts-starter`

## Features

This repository is 🔋 battery packed with:

- ⚡️ Next.js 14.x with App Router
- ⚛️ React 18.x
- ⚡️ Next.js 15.x with App Router
- ⚛️ React 19.x
- ✨ TypeScript
- 💨 Material UI — Ready to use Material Design components [check here for the usage](https://mui.com/material-ui/getting-started/usage/)
- 🎨 React Hook Form — Performant, flexible and extensible forms with easy-to-use validation
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nextjs-materia-mui-typescript-hook-form-scaffold-boilerplate-starter",
"version": "0.1.0",
"name": "react19-nextjs15-materia-mui6-typescript-hook-form-scaffold-boilerplate-starter",
"version": "2.0",
"private": true,
"scripts": {
"dev": "next dev",
Expand All @@ -24,9 +24,9 @@
"@mui/icons-material": "^5.14.9",
"@mui/material": "^5.14.10",
"dayjs": "^1.11.10",
"next": "^14.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"next": "^15.0.0-rc.0",
"react": "^19.0.0-rc-512b09b2-20240718",
"react-dom": "^19.0.0-rc-512b09b2-20240718",
"react-hook-form": "^7.46.2",
"react-icons": "^4.10.1",
"zod": "^3.22.4"
Expand All @@ -42,7 +42,7 @@
"@typescript-eslint/parser": "^5.62.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"eslint-config-next": "^14.0.1",
"eslint-config-next": "^15.0.0-rc.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const loadDataFromApi = async (slug?: string) => {
// Fetch & cache data from 2 remote APIs test
const [reactNpmData, nextJsNpmData] = await Promise.all([
getApiResponse<NpmData>({
apiEndpoint: 'https://registry.npmjs.org/react/latest',
apiEndpoint: 'https://registry.npmjs.org/react/rc',
revalidate: 60 * 60 * 24, // 24 hours cache
timeout: 5000, // 5 seconds
}),
getApiResponse<NpmData>({
apiEndpoint: 'https://registry.npmjs.org/next/latest',
apiEndpoint: 'https://registry.npmjs.org/next/rc',
revalidate: 0, // no cache
timeout: 5000, // 5 seconds
}),
Expand Down
10 changes: 6 additions & 4 deletions src/components/Homepage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import PinDropIcon from '@mui/icons-material/PinDrop';
import AutoAwesome from '@mui/icons-material/AutoAwesome';
import { Box, Typography } from '@mui/material';
import Link from 'next/link';

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

import DisplayRandomPicture from '@/components/shared/DisplayRandomPicture';
import PageFooter from '@/components/shared/PageFooter';
import ReactActionForm from '@/components/shared/ReactActionForm';
import ReactHookForm from '@/components/shared/ReactHookForm';

import { FETCH_API_CTX_VALUE, SITE_CONFIG } from '@/constants';
Expand All @@ -18,7 +19,7 @@ export default function Homepage({
<main>
<section>
<Box sx={{ textAlign: 'center' }}>
<PinDropIcon
<AutoAwesome
className='page-title'
sx={{ width: '3rem', height: '3rem' }}
/>
Expand All @@ -44,8 +45,8 @@ export default function Homepage({
sx={{ color: 'green', mt: 3 }}
>
Fetch & cache data from 2 remote APIs test: <br />
The latest React version is {reactVersion}, and the latest NextJs
version is {nextJsVersion}
The React RC version is {reactVersion}, and the NextJs RC version is{' '}
{nextJsVersion}
<Box sx={{ color: 'grey', fontSize: 10 }}>
On dev environment, you can see how long it takes on console, e.g.
getApiResponse: 0.05s
Expand All @@ -67,6 +68,7 @@ export default function Homepage({
component)
</h4>
<ClientProvider defaultValue={FETCH_API_CTX_VALUE}>
<ReactActionForm />
<ReactHookForm />
<DisplayRandomPicture />
</ClientProvider>
Expand Down
91 changes: 47 additions & 44 deletions src/components/shared/DisplayRandomPicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import { purple } from '@mui/material/colors';
import Stack from '@mui/material/Stack';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useTransition } from 'react';

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

const DisplayRandomPicture = () => {
const [imageUrl, setImageUrl] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const { fetchCount, updateClientCtx } = useClientContext<FetchApiContext>();
const { setAlertBarProps, renderAlertBar } = useAlertBar();
const renderCountRef = React.useRef(0);
const [isPending, startTransition] = useTransition();

const fetchRandomPicture = async () => {
if (loading) {
setAlertBarProps({
message: 'Please wait for the current fetch to complete',
severity: 'warning',
});
return;
}
setLoading(true);
setError('');
startTransition(async () => {
if (isPending) {
setAlertBarProps({
message: 'Please wait for the current fetch to complete',
severity: 'warning',
});
return;
}
setError('');

try {
const response = await getApiResponse<Response & { url: string }>({
apiEndpoint: 'https://picsum.photos/300/160',
timeout: 5001,
});
try {
const response = await getApiResponse<Response & { url: string }>({
apiEndpoint: 'https://picsum.photos/300/160',
timeout: 5001,
});

if (!response?.url) {
throw new Error('Error fetching the image, no response url');
}
if (!response?.url) {
throw new Error('Error fetching the image, no response url');
}

setImageUrl(response.url);
updateClientCtx({ fetchCount: fetchCount + 1 });
setAlertBarProps({
message: 'A random picture fetched successfully',
severity: 'info',
});
} catch (error) {
const errorMsg =
error instanceof Error ? error.message : 'Error fetching the image';
setImageUrl(response.url);
updateClientCtx({ fetchCount: fetchCount + 1 });
setAlertBarProps({
message: 'A random picture fetched successfully',
severity: 'info',
});
} catch (error) {
const errorMsg =
error instanceof Error ? error.message : 'Error fetching the image';

setError(errorMsg);
setAlertBarProps({
message: errorMsg,
severity: 'error',
});
setLoading(false);
} finally {
setLoading(false);
}
setError(errorMsg);
setAlertBarProps({
message: errorMsg,
severity: 'error',
});
} finally {
// setLoading(false);
}
});
};

useEffect(() => {
if (renderCountRef.current === 0 && !loading) {
if (renderCountRef.current === 0 && !isPending) {
fetchRandomPicture();
}
renderCountRef.current += 1;
Expand All @@ -83,7 +83,6 @@ const DisplayRandomPicture = () => {
spacing={2}
sx={{ position: 'relative', width: '300px', margin: '0 auto' }}
>
{error && <p>{error}</p>}
{imageUrl && (
<Avatar
alt='DisplayRandomPicture'
Expand All @@ -92,27 +91,31 @@ const DisplayRandomPicture = () => {
sx={{ width: 300, height: 150, borderRadius: '10px' }}
/>
)}
{error && <p>{error}</p>}
<div>
{loading ? <span>Loading...</span> : null} Component Render Count:{' '}
{isPending && <span>Loading...</span>} Component Render Count:{' '}
{renderCountRef.current + 1}
</div>

<SubmitButton
isSubmitting={loading}
isSubmitting={isPending}
submittingText='Fetching Picture ...'
>
<Button
variant='contained'
endIcon={<Send />}
onClick={fetchRandomPicture}
disabled={loading}
disabled={isPending}
color='secondary'
>
Get Another Picture
</Button>
</SubmitButton>
{imageUrl && (
<StyledRefreshButton onClick={fetchRandomPicture} loading={loading}>
<StyledRefreshButton
onClick={fetchRandomPicture}
loading={isPending ? 1 : 0}
>
<Avatar sx={{ width: 24, height: 24 }}>
<Autorenew />
</Avatar>
Expand All @@ -131,7 +134,7 @@ const spin = keyframes`
transform: rotate(360deg);
}
`;
const StyledRefreshButton = styled.div<{ loading?: boolean }>`
const StyledRefreshButton = styled.div<{ loading: number }>`
position: absolute;
right: 0;
top: 0;
Expand Down
Loading

0 comments on commit 6d0dd94

Please sign in to comment.