├── src
├── vite-env.d.ts
├── utils.ts
├── index.css
├── App.tsx
├── main.tsx
├── tutorial
│ ├── 02-actions
│ │ ├── 02-action.tsx
│ │ ├── 03-hook.tsx
│ │ ├── 04-complex.tsx
│ │ └── 01-traditional-approach.tsx
│ ├── 01-use
│ │ ├── 02-use-approach.tsx
│ │ └── 01-traditional-approach.tsx
│ ├── 05-render-issue
│ │ └── index.tsx
│ ├── 03-useFormStatus
│ │ ├── 02-final.tsx
│ │ └── 01-starter.tsx
│ └── 04-useOptimistic
│ │ ├── 02-final.tsx
│ │ └── 01-starter.tsx
├── final
│ ├── 05-render-issue
│ │ └── index.tsx
│ ├── 01-use
│ │ ├── 01-traditional-approach.tsx
│ │ └── 02-use-approach.tsx
│ ├── 02-actions
│ │ ├── 02-action.tsx
│ │ ├── 03-hook.tsx
│ │ ├── 04-complex.tsx
│ │ └── 01-traditional-approach.tsx
│ ├── 03-useFormStatus
│ │ ├── 01-starter.tsx
│ │ └── 02-final.tsx
│ └── 04-useOptimistic
│ │ ├── 01-starter.tsx
│ │ └── 02-final.tsx
└── assets
│ └── react.svg
├── postcss.config.js
├── tsconfig.json
├── tailwind.config.js
├── .gitignore
├── vite.config.ts
├── index.html
├── tsconfig.node.json
├── tsconfig.app.json
├── db.json
├── eslint.config.js
├── package.json
├── public
└── vite.svg
└── README.md
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export const API_URL = 'http://localhost:3001';
2 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | function App() {
2 | return (
3 |
4 | React 19 Tutorial with TypeScript
5 |
6 | );
7 | }
8 |
9 | export default App;
10 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import './index.css'
4 | import App from './App.tsx'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 | const ReactCompilerConfig = {};
4 | // https://vite.dev/config/
5 | export default defineConfig({
6 | plugins: [
7 | react({
8 | babel: {
9 | plugins: [['babel-plugin-react-compiler', ReactCompilerConfig]],
10 | },
11 | }),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React 19 Tutorial with TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedSideEffectImports": true
22 | },
23 | "include": ["vite.config.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "noUncheckedSideEffectImports": true
24 | },
25 | "include": ["src"]
26 | }
27 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "users": [
3 | {
4 | "id": "1",
5 | "name": "John Doe",
6 | "email": "johndoe@example.com"
7 | },
8 | {
9 | "id": "2",
10 | "name": "Jane Smith",
11 | "email": "janesmith@example.com"
12 | },
13 | {
14 | "id": "d6ce",
15 | "name": "susan",
16 | "email": "susan@gmail.com"
17 | },
18 | {
19 | "id": "b98d",
20 | "name": "john",
21 | "email": "john@gmail.com"
22 | },
23 | {
24 | "id": "f661",
25 | "name": "john",
26 | "email": "john@gmail.com"
27 | }
28 | ],
29 |
30 | "todos": [
31 | {
32 | "id": "1",
33 | "text": "Learn React",
34 | "completed": true
35 | },
36 | {
37 | "id": "a185",
38 | "text": "new item",
39 | "completed": true
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import globals from 'globals';
3 | import reactHooks from 'eslint-plugin-react-hooks';
4 | import reactRefresh from 'eslint-plugin-react-refresh';
5 | import tseslint from 'typescript-eslint';
6 | import reactCompiler from 'eslint-plugin-react-compiler';
7 |
8 | export default tseslint.config(
9 | { ignores: ['dist'] },
10 | {
11 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
12 | files: ['**/*.{ts,tsx}'],
13 | languageOptions: {
14 | ecmaVersion: 2020,
15 | globals: globals.browser,
16 | },
17 | plugins: {
18 | 'react-hooks': reactHooks,
19 | 'react-refresh': reactRefresh,
20 | 'react-compiler': reactCompiler,
21 | },
22 | rules: {
23 | ...reactHooks.configs.recommended.rules,
24 | 'react-refresh/only-export-components': [
25 | 'warn',
26 | { allowConstantExport: true },
27 | ],
28 | 'react-compiler/react-compiler': 'error',
29 | },
30 | }
31 | );
32 |
--------------------------------------------------------------------------------
/src/tutorial/02-actions/02-action.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { API_URL } from '../../utils';
3 |
4 | const Component = () => {
5 | return (
6 |
25 | );
26 | };
27 | export default Component;
28 |
29 | const formStyles = {
30 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
31 | input:
32 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
33 | button:
34 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
35 | };
36 |
--------------------------------------------------------------------------------
/src/tutorial/02-actions/03-hook.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useActionState } from 'react';
3 | import { API_URL } from '../../utils';
4 |
5 | const Component = () => {
6 | return (
7 |
26 | );
27 | };
28 | export default Component;
29 |
30 | const formStyles = {
31 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
32 | input:
33 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
34 | button:
35 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
36 | };
37 |
--------------------------------------------------------------------------------
/src/tutorial/01-use/02-use-approach.tsx:
--------------------------------------------------------------------------------
1 | import { use, Suspense, useState } from 'react';
2 | import axios from 'axios';
3 | import { API_URL } from '../../utils';
4 |
5 | type User = {
6 | id: string;
7 | name: string;
8 | email: string;
9 | };
10 |
11 | const Component = () => {
12 | const users = [] as User[];
13 | const [count, setCount] = useState(0);
14 |
15 | return (
16 |
17 |
setCount(count + 1)}
20 | >
21 | Count: {count}
22 |
23 |
Users
24 |
25 | {users.map((user) => (
26 |
30 | {user.name}
31 | - {user.email}
32 |
33 | ))}
34 |
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/tutorial/02-actions/04-complex.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useActionState } from 'react';
3 | import { API_URL } from '../../utils';
4 | type Status = 'success' | 'error' | 'idle';
5 | type FormState = { status: Status; name: string };
6 |
7 | const Component = () => {
8 | return (
9 |
28 | );
29 | };
30 | export default Component;
31 |
32 | const formStyles = {
33 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
34 | input:
35 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
36 | button:
37 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
38 | };
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-19-tutorial",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "server": "json-server db.json --port 3001",
8 | "dev": "concurrently \"npm run server\" \"vite\"",
9 | "build": "tsc -b && vite build",
10 | "lint": "eslint .",
11 | "preview": "vite preview"
12 | },
13 | "dependencies": {
14 | "@types/react": "19.0.2",
15 | "@types/react-dom": "19.0.2",
16 | "axios": "^1.7.9",
17 | "concurrently": "^9.1.0",
18 | "json-server": "^1.0.0-beta.3",
19 | "react": "^19.0.0",
20 | "react-dom": "^19.0.0"
21 | },
22 | "devDependencies": {
23 | "@eslint/js": "^9.17.0",
24 | "@vitejs/plugin-react": "^4.3.4",
25 | "autoprefixer": "^10.4.20",
26 | "babel-plugin-react-compiler": "^19.0.0-beta-201e55d-20241215",
27 | "eslint": "^9.17.0",
28 | "eslint-plugin-react-compiler": "^19.0.0-beta-201e55d-20241215",
29 | "eslint-plugin-react-hooks": "^5.0.0",
30 | "eslint-plugin-react-refresh": "^0.4.16",
31 | "globals": "^15.13.0",
32 | "postcss": "^8.4.49",
33 | "tailwindcss": "^3.4.17",
34 | "typescript": "~5.6.2",
35 | "typescript-eslint": "^8.18.1",
36 | "vite": "^6.0.3"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/final/05-render-issue/index.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useState } from 'react';
2 |
3 | const Parent = () => {
4 | const [count, setCount] = useState(0);
5 | const [value, setValue] = useState(0);
6 | return (
7 |
8 |
Parent Component
9 |
Count: {count}
10 |
setCount(count + 1)}
13 | >
14 | Increment
15 |
16 |
17 | {/*
*/}
18 |
19 | );
20 | };
21 |
22 | type ChildProps = {
23 | value: number;
24 | setValue: (value: number) => void;
25 | };
26 |
27 | const Child = ({ value, setValue }: ChildProps) => {
28 | console.log('Child rendered');
29 | return (
30 |
31 |
Child Component
32 |
Value: {value}
33 |
setValue(value + 1)}
36 | >
37 | Increment
38 |
39 |
40 | );
41 | };
42 | const OptimizedChild = memo(Child);
43 |
44 | export default Parent;
45 |
--------------------------------------------------------------------------------
/src/tutorial/05-render-issue/index.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useState } from 'react';
2 |
3 | const Parent = () => {
4 | const [count, setCount] = useState(0);
5 | const [value, setValue] = useState(0);
6 | return (
7 |
8 |
Parent Component
9 |
Count: {count}
10 |
setCount(count + 1)}
13 | >
14 | Increment
15 |
16 |
17 | {/*
*/}
18 |
19 | );
20 | };
21 |
22 | type ChildProps = {
23 | value: number;
24 | setValue: (value: number) => void;
25 | };
26 |
27 | const Child = ({ value, setValue }: ChildProps) => {
28 | console.log('Child rendered');
29 | return (
30 |
31 |
Child Component
32 |
Value: {value}
33 |
setValue(value + 1)}
36 | >
37 | Increment
38 |
39 |
40 | );
41 | };
42 | const OptimizedChild = memo(Child);
43 |
44 | export default Parent;
45 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/final/01-use/01-traditional-approach.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import axios from 'axios';
3 | import { API_URL } from '../../utils';
4 |
5 | type User = {
6 | id: string;
7 | name: string;
8 | email: string;
9 | };
10 |
11 | const Component = () => {
12 | const [users, setUsers] = useState([]);
13 | const [isLoading, setIsLoading] = useState(false);
14 |
15 | const fetchUsers = async () => {
16 | setIsLoading(true);
17 | try {
18 | const response = await axios.get(`${API_URL}/users`);
19 | setUsers(response.data);
20 | } catch (error) {
21 | console.log(error);
22 | } finally {
23 | setIsLoading(false);
24 | }
25 | };
26 |
27 | useEffect(() => {
28 | fetchUsers();
29 | }, []);
30 |
31 | return (
32 |
33 |
Users
34 | {isLoading ? (
35 |
Loading...
36 | ) : (
37 |
38 | {users.map((user) => (
39 |
43 | {user.name}
44 | - {user.email}
45 |
46 | ))}
47 |
48 | )}
49 |
50 | );
51 | };
52 | export default Component;
53 |
--------------------------------------------------------------------------------
/src/tutorial/01-use/01-traditional-approach.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import axios from 'axios';
3 | import { API_URL } from '../../utils';
4 |
5 | type User = {
6 | id: string;
7 | name: string;
8 | email: string;
9 | };
10 |
11 | const Component = () => {
12 | const [users, setUsers] = useState([]);
13 | const [isLoading, setIsLoading] = useState(false);
14 |
15 | const fetchUsers = async () => {
16 | setIsLoading(true);
17 | try {
18 | const response = await axios.get(`${API_URL}/users`);
19 | setUsers(response.data);
20 | } catch (error) {
21 | console.log(error);
22 | } finally {
23 | setIsLoading(false);
24 | }
25 | };
26 |
27 | useEffect(() => {
28 | fetchUsers();
29 | }, []);
30 |
31 | return (
32 |
33 |
Users
34 | {isLoading ? (
35 |
Loading...
36 | ) : (
37 |
38 | {users.map((user) => (
39 |
43 | {user.name}
44 | - {user.email}
45 |
46 | ))}
47 |
48 | )}
49 |
50 | );
51 | };
52 | export default Component;
53 |
--------------------------------------------------------------------------------
/src/tutorial/03-useFormStatus/02-final.tsx:
--------------------------------------------------------------------------------
1 | import { useFormStatus } from 'react-dom';
2 | import { useActionState } from 'react';
3 |
4 | const formAction = async (
5 | prevState: string,
6 | formData: FormData
7 | ): Promise => {
8 | await new Promise((resolve) => setTimeout(resolve, 2000));
9 | console.log(formData);
10 | return 'success';
11 | };
12 |
13 | const FirstForm = () => {
14 | const [status, actionFunction] = useActionState(
15 | formAction,
16 | 'idle'
17 | );
18 | return (
19 |
23 | );
24 | };
25 |
26 | const SecondForm = () => {
27 | const [status, actionFunction] = useActionState(
28 | formAction,
29 | 'idle'
30 | );
31 | return (
32 |
36 | );
37 | };
38 |
39 | const ParentComponent = () => {
40 | return (
41 | <>
42 |
43 |
44 | >
45 | );
46 | };
47 |
48 | const formStyles = {
49 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
50 | input:
51 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
52 | };
53 |
54 | export default ParentComponent;
55 |
--------------------------------------------------------------------------------
/src/final/02-actions/02-action.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { API_URL } from '../../utils';
3 |
4 | const Component = () => {
5 | // const formAction = async (formData: FormData): Promise => {
6 | // const name = formData.get('name') as string;
7 | // const email = formData.get('email') as string;
8 | // await new Promise((resolve) => setTimeout(resolve, 2000));
9 | // const response = await axios.post(`${API_URL}/users`, { name, email });
10 | // console.log(response.data);
11 | // };
12 | const formAction = async (formData: FormData): Promise => {
13 | const data = Object.fromEntries(formData);
14 | await new Promise((resolve) => setTimeout(resolve, 2000));
15 | const response = await axios.post(`${API_URL}/users`, data);
16 | console.log(response.data);
17 | };
18 | return (
19 |
38 | );
39 | };
40 | export default Component;
41 |
42 | const formStyles = {
43 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
44 | input:
45 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
46 | button:
47 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
48 | };
49 |
--------------------------------------------------------------------------------
/src/final/01-use/02-use-approach.tsx:
--------------------------------------------------------------------------------
1 | import { use, Suspense, useState } from 'react';
2 | import axios from 'axios';
3 | import { API_URL } from '../../utils';
4 |
5 | type User = {
6 | id: string;
7 | name: string;
8 | email: string;
9 | };
10 |
11 | // Move the data fetching logic outside the component
12 | const fetchUsers = async () => {
13 | const response = await axios.get(`${API_URL}/users`);
14 | return response.data as User[];
15 | };
16 |
17 | const Component = () => {
18 | // Use the 'use' hook to directly consume the promise
19 | const users = use(fetchUsers());
20 | const [count, setCount] = useState(0);
21 |
22 | return (
23 |
24 |
setCount(count + 1)}
27 | >
28 | Count: {count}
29 |
30 |
Users
31 |
32 | {users.map((user) => (
33 |
37 | {user.name}
38 | - {user.email}
39 |
40 | ))}
41 |
42 |
43 | );
44 | };
45 |
46 | // Wrap the component with Suspense to handle the loading state
47 | const UsersPage = () => {
48 | return (
49 | Loading...
52 | }
53 | >
54 |
55 |
56 | );
57 | };
58 |
59 | export default UsersPage;
60 |
--------------------------------------------------------------------------------
/src/final/03-useFormStatus/01-starter.tsx:
--------------------------------------------------------------------------------
1 | import { useActionState } from 'react';
2 |
3 | const formAction = async (
4 | prevState: string,
5 | formData: FormData
6 | ): Promise => {
7 | await new Promise((resolve) => setTimeout(resolve, 2000));
8 | console.log(formData);
9 | return 'success';
10 | };
11 |
12 | const FirstForm = () => {
13 | const [status, actionFunction, isPending] = useActionState(
14 | formAction,
15 | 'idle'
16 | );
17 | return (
18 |
25 | );
26 | };
27 |
28 | const SecondForm = () => {
29 | const [status, actionFunction, isPending] = useActionState(
30 | formAction,
31 | 'idle'
32 | );
33 | return (
34 |
41 | );
42 | };
43 |
44 | const ParentComponent = () => {
45 | return (
46 | <>
47 |
48 |
49 | >
50 | );
51 | };
52 |
53 | const formStyles = {
54 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
55 | input:
56 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
57 | button:
58 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
59 | };
60 |
61 | export default ParentComponent;
62 |
--------------------------------------------------------------------------------
/src/tutorial/03-useFormStatus/01-starter.tsx:
--------------------------------------------------------------------------------
1 | import { useActionState } from 'react';
2 |
3 | const formAction = async (
4 | prevState: string,
5 | formData: FormData
6 | ): Promise => {
7 | await new Promise((resolve) => setTimeout(resolve, 2000));
8 | console.log(formData);
9 | return 'success';
10 | };
11 |
12 | const FirstForm = () => {
13 | const [status, actionFunction, isPending] = useActionState(
14 | formAction,
15 | 'idle'
16 | );
17 | return (
18 |
25 | );
26 | };
27 |
28 | const SecondForm = () => {
29 | const [status, actionFunction, isPending] = useActionState(
30 | formAction,
31 | 'idle'
32 | );
33 | return (
34 |
41 | );
42 | };
43 |
44 | const ParentComponent = () => {
45 | return (
46 | <>
47 |
48 |
49 | >
50 | );
51 | };
52 |
53 | const formStyles = {
54 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
55 | input:
56 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
57 | button:
58 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
59 | };
60 |
61 | export default ParentComponent;
62 |
--------------------------------------------------------------------------------
/src/final/02-actions/03-hook.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useActionState } from 'react';
3 | import { API_URL } from '../../utils';
4 |
5 | const formAction = async (
6 | prevState: string,
7 | formData: FormData
8 | ): Promise => {
9 | try {
10 | const data = Object.fromEntries(formData);
11 | await new Promise((resolve) => setTimeout(resolve, 2000));
12 | if (data.name === 'bobo') {
13 | throw new Error('Name is invalid');
14 | }
15 | const response = await axios.post(`${API_URL}/users`, data);
16 | console.log(response.data);
17 | return 'success';
18 | } catch (error) {
19 | return 'error';
20 | }
21 | };
22 |
23 | const Component = () => {
24 | const [status, actionFunction, isPending] = useActionState(
25 | formAction,
26 | 'idle'
27 | );
28 |
29 | return (
30 |
53 | );
54 | };
55 | export default Component;
56 |
57 | const formStyles = {
58 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
59 | input:
60 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
61 | button:
62 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
63 | };
64 |
--------------------------------------------------------------------------------
/src/final/03-useFormStatus/02-final.tsx:
--------------------------------------------------------------------------------
1 | import { useFormStatus } from 'react-dom';
2 | import { useActionState } from 'react';
3 |
4 | const SubmitButton = () => {
5 | const { pending } = useFormStatus();
6 |
7 | const btnStyles =
8 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200';
9 | return (
10 |
11 | {pending ? 'Submitting...' : 'Submit'}
12 |
13 | );
14 | };
15 |
16 | const formAction = async (
17 | prevState: string,
18 | formData: FormData
19 | ): Promise => {
20 | await new Promise((resolve) => setTimeout(resolve, 2000));
21 | console.log(formData);
22 | return 'success';
23 | };
24 |
25 | const FirstForm = () => {
26 | const [status, actionFunction] = useActionState(
27 | formAction,
28 | 'idle'
29 | );
30 | return (
31 |
36 | );
37 | };
38 |
39 | const SecondForm = () => {
40 | const [status, actionFunction] = useActionState(
41 | formAction,
42 | 'idle'
43 | );
44 | return (
45 |
50 | );
51 | };
52 |
53 | const ParentComponent = () => {
54 | return (
55 | <>
56 |
57 |
58 | >
59 | );
60 | };
61 |
62 | const formStyles = {
63 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
64 | input:
65 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
66 | };
67 |
68 | export default ParentComponent;
69 |
--------------------------------------------------------------------------------
/src/final/02-actions/04-complex.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useActionState } from 'react';
3 | import { API_URL } from '../../utils';
4 | type Status = 'success' | 'error' | 'idle';
5 | type FormState = { status: Status; name: string };
6 |
7 | const formAction = async (
8 | prevState: FormState,
9 | formData: FormData
10 | ): Promise => {
11 | try {
12 | const data = Object.fromEntries(formData);
13 | await new Promise((resolve) => setTimeout(resolve, 2000));
14 | if (data.name === 'bobo') {
15 | throw new Error('Name is invalid');
16 | }
17 | const response = await axios.post(`${API_URL}/users`, data);
18 | console.log(response.data);
19 | return { status: 'success', name: data.name as string };
20 | } catch (error) {
21 | return { status: 'error', name: prevState.name };
22 | }
23 | };
24 |
25 | const Component = () => {
26 | const [state, actionFunction, isPending] = useActionState<
27 | FormState,
28 | FormData
29 | >(formAction, {
30 | status: 'idle',
31 | name: '',
32 | });
33 |
34 | return (
35 |
65 | );
66 | };
67 | export default Component;
68 |
69 | const formStyles = {
70 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
71 | input:
72 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
73 | button:
74 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
75 | };
76 |
--------------------------------------------------------------------------------
/src/final/02-actions/01-traditional-approach.tsx:
--------------------------------------------------------------------------------
1 | import { useState, FormEvent } from 'react';
2 | import axios from 'axios';
3 | import { API_URL } from '../../utils';
4 | type Status = 'success' | 'error' | 'idle';
5 |
6 | const Component = () => {
7 | const [name, setName] = useState('');
8 | const [email, setEmail] = useState('');
9 | const [isPending, setIsPending] = useState(false);
10 | const [status, setStatus] = useState('idle');
11 |
12 | const handleSubmit = async (e: FormEvent) => {
13 | e.preventDefault();
14 | setIsPending(true);
15 | setStatus('idle');
16 |
17 | try {
18 | await new Promise((resolve) => setTimeout(resolve, 1000));
19 | if (name === 'bobo') {
20 | throw new Error('Name is invalid');
21 | }
22 | const response = await axios.post(`${API_URL}/users`, {
23 | name,
24 | email,
25 | });
26 | console.log(response.data);
27 | // Clear form after successful submission
28 | setName('');
29 | setEmail('');
30 | setStatus('success');
31 | } catch (err) {
32 | setStatus('error');
33 | } finally {
34 | setIsPending(false);
35 | }
36 | };
37 |
38 | return (
39 |
66 | );
67 | };
68 | export default Component;
69 |
70 | const formStyles = {
71 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
72 | input:
73 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
74 | button:
75 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
76 | };
77 |
--------------------------------------------------------------------------------
/src/tutorial/02-actions/01-traditional-approach.tsx:
--------------------------------------------------------------------------------
1 | import { useState, FormEvent } from 'react';
2 | import axios from 'axios';
3 | import { API_URL } from '../../utils';
4 | type Status = 'success' | 'error' | 'idle';
5 |
6 | const Component = () => {
7 | const [name, setName] = useState('');
8 | const [email, setEmail] = useState('');
9 | const [isPending, setIsPending] = useState(false);
10 | const [status, setStatus] = useState('idle');
11 |
12 | const handleSubmit = async (e: FormEvent) => {
13 | e.preventDefault();
14 | setIsPending(true);
15 | setStatus('idle');
16 |
17 | try {
18 | await new Promise((resolve) => setTimeout(resolve, 1000));
19 | if (name === 'bobo') {
20 | throw new Error('Name is invalid');
21 | }
22 | const response = await axios.post(`${API_URL}/users`, {
23 | name,
24 | email,
25 | });
26 | console.log(response.data);
27 | // Clear form after successful submission
28 | setName('');
29 | setEmail('');
30 | setStatus('success');
31 | } catch (err) {
32 | setStatus('error');
33 | } finally {
34 | setIsPending(false);
35 | }
36 | };
37 |
38 | return (
39 |
66 | );
67 | };
68 | export default Component;
69 |
70 | const formStyles = {
71 | container: 'max-w-md mx-auto mt-24 p-8 space-y-4 bg-white rounded shadow',
72 | input:
73 | 'w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500',
74 | button:
75 | 'w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-200',
76 | };
77 |
--------------------------------------------------------------------------------
/src/tutorial/04-useOptimistic/02-final.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | useEffect,
3 | useState,
4 | useActionState,
5 | useOptimistic,
6 | useTransition,
7 | } from 'react';
8 | import { useFormStatus } from 'react-dom';
9 | import axios from 'axios';
10 | import { API_URL } from '../../utils';
11 |
12 | type FormState = {
13 | error: string | null;
14 | success: boolean;
15 | };
16 |
17 | type Todo = {
18 | id: string;
19 | text: string;
20 | completed: boolean;
21 | };
22 |
23 | function SubmitButton() {
24 | const { pending } = useFormStatus();
25 |
26 | return (
27 |
32 | {pending ? 'Adding...' : 'Add'}
33 |
34 | );
35 | }
36 |
37 | const TodoList = () => {
38 | const [todos, setTodos] = useState([]);
39 |
40 | const [formState, formAction] = useActionState(
41 | async (prevState: FormState, formData: FormData): Promise => {
42 | const text = formData.get('todo') as string;
43 | if (!text?.trim()) {
44 | return { error: 'Todo text is required !!!', success: false };
45 | }
46 | try {
47 | // Actual API call
48 | await axios.post(`${API_URL}/todos`, { text, completed: false });
49 | await fetchTodos(); // Refresh the list
50 | return { error: null, success: true };
51 | } catch (error) {
52 | return { error: 'Failed to add todo', success: false };
53 | }
54 | },
55 | { error: null, success: false }
56 | );
57 |
58 | const fetchTodos = async () => {
59 | const response = await axios.get(`${API_URL}/todos`);
60 | setTodos(response.data);
61 | };
62 |
63 | const toggleTodo = (todo: Todo) => todo;
64 |
65 | useEffect(() => {
66 | fetchTodos();
67 | }, []);
68 |
69 | return (
70 |
71 |
Todo List
72 |
73 |
95 |
96 |
109 |
110 | );
111 | };
112 |
113 | export default TodoList;
114 |
--------------------------------------------------------------------------------
/src/final/04-useOptimistic/01-starter.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useActionState } from 'react';
2 | import { useFormStatus } from 'react-dom';
3 | import axios from 'axios';
4 | import { API_URL } from '../../utils';
5 |
6 | type FormState = {
7 | error: string | null;
8 | success: boolean;
9 | };
10 |
11 | type Todo = {
12 | id: string;
13 | text: string;
14 | completed: boolean;
15 | };
16 |
17 | function SubmitButton() {
18 | const { pending } = useFormStatus();
19 |
20 | return (
21 |
26 | {pending ? 'Adding...' : 'Add'}
27 |
28 | );
29 | }
30 |
31 | const TodoList = () => {
32 | const [todos, setTodos] = useState([]);
33 |
34 | const fetchTodos = async () => {
35 | await new Promise((resolve) => setTimeout(resolve, 2000));
36 | const response = await axios.get(`${API_URL}/todos`);
37 | setTodos(response.data);
38 | };
39 | // Action handler for form submission
40 | const [formState, formAction] = useActionState(
41 | async (prevState: FormState, formData: FormData): Promise => {
42 | const text = formData.get('todo') as string;
43 | if (!text?.trim()) {
44 | return { error: 'Todo text is required !!!', success: false };
45 | }
46 | try {
47 | // Actual API call
48 | await axios.post(`${API_URL}/todos`, { text, completed: false });
49 | await fetchTodos(); // Refresh the list
50 | return { error: null, success: true };
51 | } catch (error) {
52 | return { error: 'Failed to add todo', success: false };
53 | }
54 | },
55 | { error: null, success: false }
56 | );
57 |
58 | const toggleTodo = async (todo: Todo) => {
59 | try {
60 | await axios.put(`${API_URL}/todos/${todo.id}`, {
61 | ...todo,
62 | completed: !todo.completed,
63 | });
64 | await fetchTodos();
65 | } catch (error) {
66 | console.error('Failed to update todo:', error);
67 | }
68 | };
69 |
70 | useEffect(() => {
71 | fetchTodos();
72 | }, []);
73 |
74 | return (
75 |
76 |
Todo List
77 |
78 |
100 |
101 |
110 | {formState.success &&
Success!!!
}
111 | {formState.error && (
112 |
{formState.error}
113 | )}
114 |
115 | );
116 | };
117 |
118 | export default TodoList;
119 |
--------------------------------------------------------------------------------
/src/tutorial/04-useOptimistic/01-starter.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useActionState } from 'react';
2 | import { useFormStatus } from 'react-dom';
3 | import axios from 'axios';
4 | import { API_URL } from '../../utils';
5 |
6 | type FormState = {
7 | error: string | null;
8 | success: boolean;
9 | };
10 |
11 | type Todo = {
12 | id: string;
13 | text: string;
14 | completed: boolean;
15 | };
16 |
17 | function SubmitButton() {
18 | const { pending } = useFormStatus();
19 |
20 | return (
21 |
26 | {pending ? 'Adding...' : 'Add'}
27 |
28 | );
29 | }
30 |
31 | const TodoList = () => {
32 | const [todos, setTodos] = useState([]);
33 |
34 | const fetchTodos = async () => {
35 | await new Promise((resolve) => setTimeout(resolve, 2000));
36 | const response = await axios.get(`${API_URL}/todos`);
37 | setTodos(response.data);
38 | };
39 | // Action handler for form submission
40 | const [formState, formAction] = useActionState(
41 | async (prevState: FormState, formData: FormData): Promise => {
42 | const text = formData.get('todo') as string;
43 | if (!text?.trim()) {
44 | return { error: 'Todo text is r equired !!!', success: false };
45 | }
46 | try {
47 | // Actual API call
48 | await axios.post(`${API_URL}/todos`, { text, completed: false });
49 | await fetchTodos(); // Refresh the list
50 | return { error: null, success: true };
51 | } catch (error) {
52 | return { error: 'Failed to add todo', success: false };
53 | }
54 | },
55 | { error: null, success: false }
56 | );
57 |
58 | const toggleTodo = async (todo: Todo) => {
59 | try {
60 | await axios.put(`${API_URL}/todos/${todo.id}`, {
61 | ...todo,
62 | completed: !todo.completed,
63 | });
64 | await fetchTodos();
65 | } catch (error) {
66 | console.error('Failed to update todo:', error);
67 | }
68 | };
69 |
70 | useEffect(() => {
71 | fetchTodos();
72 | }, []);
73 |
74 | return (
75 |
76 |
Todo List
77 |
78 |
100 |
101 |
110 | {formState.success &&
Success!!!
}
111 | {formState.error && (
112 |
{formState.error}
113 | )}
114 |
115 | );
116 | };
117 |
118 | export default TodoList;
119 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/final/04-useOptimistic/02-final.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | useEffect,
3 | useState,
4 | useActionState,
5 | useOptimistic,
6 | useTransition,
7 | } from 'react';
8 | import { useFormStatus } from 'react-dom';
9 | import axios from 'axios';
10 | import { API_URL } from '../../utils';
11 |
12 | type FormState = {
13 | error: string | null;
14 | success: boolean;
15 | };
16 |
17 | type Todo = {
18 | id: string;
19 | text: string;
20 | completed: boolean;
21 | };
22 |
23 | function SubmitButton() {
24 | const { pending } = useFormStatus();
25 |
26 | return (
27 |
32 | {pending ? 'Adding...' : 'Add'}
33 |
34 | );
35 | }
36 |
37 | const TodoList = () => {
38 | const [todos, setTodos] = useState([]);
39 | const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos);
40 | const [isPending, startTransition] = useTransition();
41 |
42 | const fetchTodos = async () => {
43 | const response = await axios.get(`${API_URL}/todos`);
44 | setTodos(response.data);
45 | };
46 | // Action handler for form submission
47 | const [formState, formAction] = useActionState(
48 | async (prevState: FormState, formData: FormData): Promise => {
49 | const text = formData.get('todo') as string;
50 | if (!text?.trim()) {
51 | return { error: 'Todo text is required !!!', success: false };
52 | }
53 | try {
54 | //
55 | setOptimisticTodos((prevTodos) => [
56 | ...prevTodos,
57 | { id: 'OPTIMISTIC : ', text, completed: false },
58 | ]);
59 | // Actual API call
60 | await axios.post(`${API_URL}/todos`, { text, completed: false });
61 | await fetchTodos(); // Refresh the list
62 | return { error: null, success: true };
63 | } catch (error) {
64 | return { error: 'Failed to add todo', success: false };
65 | }
66 | },
67 | { error: null, success: false }
68 | );
69 |
70 | const toggleTodo = (todo: Todo) => {
71 | startTransition(async () => {
72 | const todoToUpdate = { ...todo, completed: !todo.completed };
73 | setOptimisticTodos((prevTodos) =>
74 | prevTodos.map((t) => (t.id === todo.id ? todoToUpdate : t))
75 | );
76 | try {
77 | await new Promise((resolve) => setTimeout(resolve, 2000));
78 | await axios.put(`${API_URL}/todos/${todo.id}`, {
79 | ...todo,
80 | completed: !todo.completed,
81 | });
82 | await fetchTodos();
83 | } catch (error) {
84 | setOptimisticTodos((prevTodos) =>
85 | prevTodos.map((t) => (t.id === todo.id ? todo : t))
86 | );
87 | }
88 | });
89 | };
90 |
91 | useEffect(() => {
92 | fetchTodos();
93 | }, []);
94 |
95 | return (
96 |
97 |
Todo List
98 |
99 |
121 |
122 |
131 | {formState.success &&
Success!!!
}
132 | {formState.error && (
133 |
{formState.error}
134 | )}
135 |
136 | );
137 | };
138 |
139 | export default TodoList;
140 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React 19 Tutorial
2 |
3 | ## Bootstrap Project
4 |
5 | - `npm i `
6 | - `npm run dev`
7 |
8 | ## Project Overview
9 |
10 | Will cover the entire setup of the project (step by step) towards the end of the tutorial.
11 |
12 | Boilerplate Vite React-TS. Added:
13 |
14 | - latest react and react-dom, as well as types for both
15 | - JSON Server for mock API (localhost:3001)
16 | [JSON Server](https://www.npmjs.com/package/json-server)
17 | - Axios for API calls
18 | - TailwindCSS for styling
19 | - src/utils.ts for API_URL
20 |
21 | Reference package.json
22 |
23 | ## React 19
24 |
25 | - src/final - complete source code
26 | - src/tutorial - sandbox for each step where we will test the features
27 |
28 | ## use
29 |
30 | use is a React API that lets you read the value of a resource like a Promise or context.
31 |
32 | - Key Features:
33 | - More flexible than traditional hooks - can be used in loops and conditionals
34 | - Works with both Promises and Context
35 | - Integrates with Suspense and Error Boundaries- Can be used anywhere in a component (not just at the top level)
36 |
37 | Common Use Cases:
38 |
39 | ```tsx
40 | // Reading Context
41 | const theme = use(ThemeContext);
42 |
43 | // Handling Promises
44 | function Message({ messagePromise }) {
45 | const message = use(messagePromise);
46 | return {message}
;
47 | }
48 | ```
49 |
50 | Important Notes:
51 |
52 | - Must be called inside a Component or Hook
53 | - For Server Components, prefer async/await over use
54 | - When using with Promises, wrap components in Suspense/Error Boundaries
55 | - Cannot be used in try-catch blocks
56 |
57 | Best Practice:
58 |
59 | ```tsx
60 | // Preferred: Create Promises in Server Components
61 | }>
62 |
63 |
64 | ```
65 |
66 | ## React Actions
67 |
68 | - React 19 introduced a new way to handle form submissions using the `action` attribute.
69 | - This approach simplifies form handling and provides built-in loading states and error handling.
70 | - if you are familiar with `server actions` in Next.js, you will feel right at home.
71 |
72 | Evolution of Form Handling
73 |
74 | 1. Traditional HTML Forms
75 |
76 | ```tsx
77 |
81 | ```
82 |
83 | - Simple but requires page refresh
84 | - No built-in state management
85 | - Limited user feedback
86 |
87 | 2. React's Traditional Approach
88 |
89 | ```tsx
90 | function Form() {
91 | const [isPending, setIsPending] = useState(false);
92 |
93 | const handleSubmit = async (e) => {
94 | e.preventDefault();
95 | setIsPending(true);
96 | // handle submission
97 | };
98 |
99 | return ;
100 | }
101 | ```
102 |
103 | - Client-side handling
104 | - Manual state management
105 | - Requires preventDefault()
106 | - requires handling loading and error states
107 |
108 | 3. New React Actions (React 19)
109 |
110 | ```tsx
111 | function Form() {
112 | async function formAction(formData: FormData) {
113 | // handle submission
114 | }
115 |
116 | return ;
117 | }
118 | ```
119 |
120 | - No preventDefault() needed
121 | - Automatic FormData handling
122 | - Form reset on success
123 |
124 | - Built-in loading state (useActionState)
125 | - easier Error handling (useActionState)
126 |
127 | ## useActionState hook
128 |
129 | useActionState Hook
130 |
131 | Purpose
132 |
133 | A React hook that manages state for asynchronous actions, particularly useful for form submissions.
134 |
135 | ```tsx
136 | const [state, dispatch, isPending] = useActionState(action, initialState);
137 | ```
138 |
139 | Returns
140 |
141 | - state: Current state value
142 | - dispatch: Function to trigger the action
143 | - isPending: Boolean indicating if action is processing
144 |
145 | Example
146 |
147 | ```tsx
148 | function Form() {
149 | const [state, formAction, isPending] = useActionState(
150 | async (prevState, formData) => {
151 | // Process form data
152 | return { status: 'success' };
153 | },
154 | { status: 'idle' }
155 | );
156 |
157 | return (
158 |
163 | );
164 | }
165 | ```
166 |
167 | Key Points
168 |
169 | - Combines state management with async actions
170 | - Automatically handles loading states
171 | - Type-safe when used with TypeScript
172 | - Designed to work with form submissions
173 | - Similar to useReducer but with built-in async support
174 |
175 | Types
176 |
177 | ```tsx
178 | useActionState(action, initialState);
179 | ```
180 |
181 | ## useFormStatus hook
182 |
183 | ## useOptimistic hook
184 |
185 | ## useTransition hook
186 |
187 | ## compiler
188 |
189 | will cover at the very end of the tutorial
190 |
191 | ## Create React 19 TypeScript Project
192 |
193 | - Node.js Version ?
194 |
195 | ### Bootstrap Vite Project
196 |
197 | ```bash
198 | npm create vite@latest react-19-tutorial -- --template react-ts
199 | ```
200 |
201 | ### React 19
202 |
203 | ```json
204 | "dependencies": {
205 | "react": "^18.3.1",
206 | "react-dom": "^18.3.1"
207 | }
208 | ```
209 |
210 | ### React 19
211 |
212 | libraries
213 |
214 | ```bash
215 | npm install react@19.0.0 react-dom@19.0.0
216 | ```
217 |
218 | types
219 |
220 | ```bash
221 | npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0
222 |
223 | ```
224 |
225 | ### React Compiler
226 |
227 | ```tsx
228 | import { memo, useState } from 'react';
229 |
230 | const Parent = () => {
231 | const [count, setCount] = useState(0);
232 | const [value, setValue] = useState(0);
233 | return (
234 |
235 |
Parent Component
236 |
Count: {count}
237 |
setCount(count + 1)}>Increment
238 |
239 | {/*
*/}
240 |
241 | );
242 | };
243 | const Child = ({
244 | value,
245 | setValue,
246 | }: {
247 | value: number;
248 | setValue: (value: number) => void;
249 | }) => {
250 | console.log('Child rendered');
251 | return (
252 |
253 |
Child Component
254 |
Value: {value}
255 |
setValue(value + 1)}>Increment
256 |
257 | );
258 | };
259 | const OptimizedChild = memo(Child);
260 |
261 | export default Parent;
262 | ```
263 |
264 | ```bash
265 | npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta
266 | ```
267 |
268 | eslint.config.js
269 |
270 | ```js
271 |
272 | import reactCompiler from 'eslint-plugin-react-compiler';
273 |
274 | export default tseslint.config(
275 | ...
276 | plugins: {
277 | ...
278 | 'react-compiler': reactCompiler,
279 | },
280 | rules: {
281 | ...
282 | 'react-compiler/react-compiler': 'error',
283 | },
284 |
285 | );
286 |
287 | ```
288 |
289 | eslint.config.js
290 |
291 | ```js
292 | import js from '@eslint/js';
293 | import globals from 'globals';
294 | import reactHooks from 'eslint-plugin-react-hooks';
295 | import reactRefresh from 'eslint-plugin-react-refresh';
296 | import tseslint from 'typescript-eslint';
297 | import reactCompiler from 'eslint-plugin-react-compiler';
298 |
299 | export default tseslint.config(
300 | { ignores: ['dist'] },
301 | {
302 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
303 | files: ['**/*.{ts,tsx}'],
304 | languageOptions: {
305 | ecmaVersion: 2020,
306 | globals: globals.browser,
307 | },
308 | plugins: {
309 | 'react-hooks': reactHooks,
310 | 'react-refresh': reactRefresh,
311 | 'react-compiler': reactCompiler,
312 | },
313 | rules: {
314 | ...reactHooks.configs.recommended.rules,
315 | 'react-refresh/only-export-components': [
316 | 'warn',
317 | { allowConstantExport: true },
318 | ],
319 | 'react-compiler/react-compiler': 'error',
320 | },
321 | }
322 | );
323 | ```
324 |
325 | vite.config.ts
326 |
327 | ```ts
328 | import { defineConfig } from 'vite';
329 | import react from '@vitejs/plugin-react';
330 | const ReactCompilerConfig = {};
331 | // https://vite.dev/config/
332 | export default defineConfig({
333 | plugins: [
334 | react({
335 | babel: {
336 | plugins: [['babel-plugin-react-compiler', ReactCompilerConfig]],
337 | },
338 | }),
339 | ],
340 | });
341 | ```
342 |
--------------------------------------------------------------------------------