├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.jsx ├── components │ ├── action │ │ ├── Final.jsx │ │ └── Starter.jsx │ ├── auto-memo │ │ ├── Button.jsx │ │ ├── Counter.jsx │ │ └── ShowCount.jsx │ ├── forwardRef │ │ └── ForwardRef.jsx │ ├── use-hook-context │ │ ├── Final.jsx │ │ └── Starter.jsx │ ├── use-hook-data-fetching │ │ ├── Final.jsx │ │ └── Starter.jsx │ ├── use-hook-promise │ │ ├── Final.jsx │ │ └── Starter.jsx │ ├── useFormState │ │ ├── Final.jsx │ │ └── Starter.jsx │ ├── useFormStatus │ │ └── Final.jsx │ └── useOptimistic │ │ ├── Final.jsx │ │ └── Starter.jsx ├── index.css └── main.jsx ├── tailwind.config.js └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:react/recommended", 7 | "plugin:react/jsx-runtime", 8 | "plugin:react-hooks/recommended", 9 | ], 10 | ignorePatterns: ["dist", ".eslintrc.cjs"], 11 | parserOptions: { ecmaVersion: "latest", sourceType: "module" }, 12 | settings: { react: { version: "18.2" } }, 13 | plugins: ["react-refresh"], 14 | rules: { 15 | "react/jsx-no-target-blank": "off", 16 | "react-refresh/only-export-components": [ 17 | "warn", 18 | { allowConstantExport: true }, 19 | ], 20 | "react/prop-types": 0, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react19", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.0-canary-56e20051c-20240311", 14 | "react-dom": "^18.3.0-canary-56e20051c-20240311" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.2.64", 18 | "@types/react-dom": "^18.2.21", 19 | "@vitejs/plugin-react": "^4.2.1", 20 | "autoprefixer": "^10.4.18", 21 | "eslint": "^8.57.0", 22 | "eslint-plugin-react": "^7.34.0", 23 | "eslint-plugin-react-hooks": "^4.6.0", 24 | "eslint-plugin-react-refresh": "^0.4.5", 25 | "postcss": "^8.4.35", 26 | "tailwindcss": "^3.4.1", 27 | "vite": "^5.1.6" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Example4 from "./components/action/Final"; 3 | import Counter from "./components/auto-memo/Counter"; 4 | import InputContainer from "./components/forwardRef/ForwardRef"; 5 | import Example3 from "./components/use-hook-context/Final"; 6 | import Example1 from "./components/use-hook-data-fetching/Final"; 7 | import Example2 from "./components/use-hook-promise/Final"; 8 | import Example6 from "./components/useFormState/Final"; 9 | import Example5 from "./components/useFormStatus/Final"; 10 | import Example7 from "./components/useOptimistic/Final"; 11 | 12 | export default function App() { 13 | const [example, setExample] = useState(""); 14 | 15 | const handleChange = (e) => { 16 | setExample(e.target.value); 17 | }; 18 | 19 | // choose which example to render 20 | let content; 21 | 22 | if (example === "example-1") { 23 | content = ; 24 | } 25 | if (example === "example-2") { 26 | content = ; 27 | } 28 | if (example === "example-3") { 29 | content = ; 30 | } 31 | if (example === "action") { 32 | content = ; 33 | } 34 | if (example === "useFormStatus") { 35 | content = ; 36 | } 37 | if (example === "useFormState") { 38 | content = ( 39 | <> 40 | 41 | 42 | 43 | ); 44 | } 45 | if (example === "useOptimistic") { 46 | content = ; 47 | } 48 | if (example === "automemo") { 49 | content = ; 50 | } 51 | if (example === "forwardRef") { 52 | content = ; 53 | } 54 | 55 | return ( 56 |
57 |

58 | What's coming in React 19 59 |

60 | 61 |
62 | I have created some examples to explore the new features. You 63 | can explore the examples below: 64 |
65 | 66 |
67 | 93 |
94 | 95 |
{content}
96 |
97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/components/action/Final.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | 3 | // PostItem component 4 | const PostItem = ({ post }) => { 5 | return ( 6 |
7 |

{post.title}

8 |

{post.body}

9 |
10 | ); 11 | }; 12 | // PostForm component 13 | const PostForm = ({ addPost }) => { 14 | const formRef = useRef(); 15 | 16 | const formAction = async (formData) => { 17 | // delay 18 | await new Promise((resolve) => { 19 | setTimeout(() => { 20 | resolve(); 21 | }, 1000); 22 | }); 23 | 24 | addPost({ title: formData.get("title"), body: formData.get("body") }); 25 | formRef.current.reset(); 26 | }; 27 | 28 | return ( 29 |
34 |
35 | 41 | 48 |
49 |
50 | 56 | 63 |
64 |
65 | 71 |
72 |
73 | ); 74 | }; 75 | 76 | // Posts component 77 | export default function Posts() { 78 | const [posts, setPosts] = useState([]); 79 | 80 | const addPost = (newPost) => { 81 | setPosts((prevPosts) => [...prevPosts, newPost]); 82 | }; 83 | 84 | return ( 85 | <> 86 | 87 | {posts.map((post, index) => ( 88 | 89 | ))} 90 | 91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /src/components/action/Starter.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | // PostItem component 4 | const PostItem = ({ post }) => { 5 | return ( 6 |
7 |

{post.title}

8 |

{post.body}

9 |
10 | ); 11 | }; 12 | // PostForm component 13 | const PostForm = ({ addPost }) => { 14 | const [title, setTitle] = useState(""); 15 | const [body, setBody] = useState(""); 16 | 17 | const handleSubmit = async (e) => { 18 | e.preventDefault(); 19 | 20 | // delay 21 | await new Promise((resolve) => { 22 | setTimeout(() => { 23 | resolve(); 24 | }, 1000); 25 | }); 26 | 27 | addPost({ title, body }); 28 | }; 29 | 30 | return ( 31 |
35 |
36 | 42 | setTitle(e.target.value)} 49 | /> 50 |
51 |
52 | 58 | 66 |
67 |
68 | 74 |
75 |
76 | ); 77 | }; 78 | 79 | // Posts component 80 | export default function Posts() { 81 | const [posts, setPosts] = useState([]); 82 | 83 | const addPost = (newPost) => { 84 | setPosts((prevPosts) => [...prevPosts, newPost]); 85 | }; 86 | 87 | return ( 88 | <> 89 | 90 | {posts.map((post, index) => ( 91 | 92 | ))} 93 | 94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /src/components/auto-memo/Button.jsx: -------------------------------------------------------------------------------- 1 | export default function Button({ handleClick, children }) { 2 | console.log(`rendering button ${children}`); 3 | 4 | return ( 5 |

6 | 13 |

14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/auto-memo/Counter.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | import Button from "./Button"; 3 | import ShowCount from "./ShowCount"; 4 | 5 | export default function Counter() { 6 | const [count1, setCount1] = useState(0); 7 | const [count2, setCount2] = useState(0); 8 | 9 | const incrementByOne = () => { 10 | setCount1((prevCount) => prevCount + 1); 11 | }; 12 | 13 | const incrementByFive = () => { 14 | setCount2((prevCount) => prevCount + 5); 15 | }; 16 | 17 | const isEvenOrOdd = useCallback(() => { 18 | let i = 0; 19 | while (i < 1000000000) i += 1; // costly operation 20 | return count1 % 2 === 0; 21 | }, [count1]); 22 | 23 | return ( 24 |
25 |
26 | 27 | {isEvenOrOdd ? "Even" : "Odd"} 28 | 29 |
30 | 31 |
32 | 33 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/auto-memo/ShowCount.jsx: -------------------------------------------------------------------------------- 1 | export default function ShowCount({ title, count }) { 2 | console.log(`rendering ${title}....`); 3 | 4 | return ( 5 |
6 | {title} is {count} 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/forwardRef/ForwardRef.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | function Input({ ref }) { 4 | useEffect(() => { 5 | ref.current.focus(); 6 | }, [ref]); 7 | 8 | return ( 9 |
10 | 15 |
16 | ); 17 | } 18 | 19 | export default function InputContainer() { 20 | const ref = useRef(null); 21 | 22 | return ; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/use-hook-context/Final.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, use, useState } from "react"; 2 | 3 | const ThemeContext = createContext(); 4 | 5 | const ThemeProvider = ({ children }) => { 6 | const [theme, setTheme] = useState("light"); 7 | 8 | const toggleTheme = () => { 9 | setTheme((prevState) => (prevState === "light" ? "dark" : "light")); 10 | }; 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | }; 18 | 19 | function ThemeCard() { 20 | const { theme, toggleTheme } = use(ThemeContext); 21 | 22 | return ( 23 |
24 |
31 | Lorem ipsum dolor sit amet consectetur adipisicing elit. 32 | Suscipit dolores illo, reprehenderit voluptate recusandae 33 | perspiciatis quam, pariatur commodi possimus officiis quas vitae 34 | voluptatum. Magni aliquam eligendi itaque possimus sit dolorem. 35 |
36 |
37 | 43 |
44 |
45 | ); 46 | } 47 | 48 | export default function Theme() { 49 | return ( 50 | 51 | 52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/components/use-hook-context/Starter.jsx: -------------------------------------------------------------------------------- 1 | export default function ThemeCard() { 2 | return ( 3 |
4 |
5 | Lorem ipsum dolor sit amet consectetur adipisicing elit. 6 | Suscipit dolores illo, reprehenderit voluptate recusandae 7 | perspiciatis quam, pariatur commodi possimus officiis quas vitae 8 | voluptatum. Magni aliquam eligendi itaque possimus sit dolorem. 9 |
10 |
11 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/use-hook-data-fetching/Final.jsx: -------------------------------------------------------------------------------- 1 | import { Suspense, use } from "react"; 2 | 3 | const fetchJokes = async () => { 4 | const res = await fetch("https://api.chucknorris.io/jokes/random"); 5 | return res.json(); // returns a promise 6 | }; 7 | 8 | const JokeItem = () => { 9 | const joke = use(fetchJokes()); 10 | 11 | return ( 12 |

13 | {joke.value} 14 |

15 | ); 16 | }; 17 | export default function Joke() { 18 | return ( 19 |
20 | 23 | Loading... 24 | 25 | } 26 | > 27 | 28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/use-hook-data-fetching/Starter.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function Joke() { 4 | const [joke, setJoke] = useState([]); 5 | const [loading, setLoading] = useState(false); 6 | 7 | useEffect(() => { 8 | const fetchJokes = async () => { 9 | try { 10 | setLoading(true); 11 | const res = await fetch( 12 | "https://api.chucknorris.io/jokes/random" 13 | ); 14 | const data = await res.json(); 15 | setJoke(data); 16 | } catch (err) { 17 | console.error(err); 18 | } finally { 19 | setLoading(false); 20 | } 21 | }; 22 | 23 | fetchJokes(); 24 | }, []); 25 | 26 | if (loading) { 27 | return ( 28 |

29 | Loading... 30 |

31 | ); 32 | } 33 | 34 | return ( 35 |
36 |

37 | {joke.value} 38 |

39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/use-hook-promise/Final.jsx: -------------------------------------------------------------------------------- 1 | import { Suspense, use, useState } from "react"; 2 | 3 | const fetchMessage = async () => { 4 | return new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve("🚀"); 7 | }, 1000); 8 | }); 9 | }; 10 | 11 | const MessageOutput = ({ messagePromise }) => { 12 | const messageContent = use(messagePromise); 13 | 14 | return

Here is the message: {messageContent}

; 15 | }; 16 | 17 | function MessageContainer({ messagePromise }) { 18 | return ( 19 | ⌛ Downloading message...

} 21 | > 22 | 23 |
24 | ); 25 | } 26 | 27 | export default function Message() { 28 | const [show, setShow] = useState(false); 29 | const [messagePromise, setMessagePromise] = useState(null); 30 | 31 | const download = () => { 32 | setMessagePromise(fetchMessage()); 33 | setShow(true); 34 | }; 35 | 36 | return show ? ( 37 | 38 | ) : ( 39 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/components/use-hook-promise/Starter.jsx: -------------------------------------------------------------------------------- 1 | export default function Message() { 2 | return ( 3 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/useFormState/Final.jsx: -------------------------------------------------------------------------------- 1 | import { useFormState } from "react-dom"; 2 | 3 | const AddToCartForm = ({ itemID, itemTitle }) => { 4 | const [message, formAction] = useFormState( 5 | addToCart, 6 | "Click the button to add to cart" 7 | ); 8 | 9 | return ( 10 |
14 |

{itemTitle}

15 | 16 | 22 |
{message}
23 |
24 | ); 25 | }; 26 | 27 | const addToCart = (prevState, formData) => { 28 | const id = formData.get("itemID"); 29 | 30 | if (id === "1") { 31 | return "Added to cart"; 32 | } 33 | return "Out of stock"; 34 | }; 35 | 36 | export default AddToCartForm; 37 | -------------------------------------------------------------------------------- /src/components/useFormState/Starter.jsx: -------------------------------------------------------------------------------- 1 | const AddToCartForm = ({ itemID, itemTitle }) => { 2 | return ( 3 |
4 |

{itemTitle}

5 | 6 | 12 |
Some message here
13 |
14 | ); 15 | }; 16 | 17 | export default AddToCartForm; 18 | -------------------------------------------------------------------------------- /src/components/useFormStatus/Final.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | import { useFormStatus } from "react-dom"; 3 | 4 | // PostItem component 5 | const PostItem = ({ post }) => { 6 | return ( 7 |
8 |

{post.title}

9 |

{post.body}

10 |
11 | ); 12 | }; 13 | 14 | const Button = () => { 15 | const { pending } = useFormStatus(); // true or false 16 | 17 | return ( 18 | 25 | ); 26 | }; 27 | // PostForm component 28 | const PostForm = ({ addPost }) => { 29 | const formRef = useRef(); 30 | 31 | const formAction = async (formData) => { 32 | // delay 33 | await new Promise((resolve) => { 34 | setTimeout(() => { 35 | resolve(); 36 | }, 1000); 37 | }); 38 | 39 | addPost({ title: formData.get("title"), body: formData.get("body") }); 40 | formRef.current.reset(); 41 | }; 42 | 43 | return ( 44 |
49 |
50 | 56 | 63 |
64 |
65 | 71 | 78 |
79 |
80 |
82 |
83 | ); 84 | }; 85 | 86 | // Posts component 87 | export default function Posts() { 88 | const [posts, setPosts] = useState([]); 89 | 90 | const addPost = (newPost) => { 91 | setPosts((prevPosts) => [...prevPosts, newPost]); 92 | }; 93 | 94 | return ( 95 | <> 96 | 97 | {posts.map((post, index) => ( 98 | 99 | ))} 100 | 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /src/components/useOptimistic/Final.jsx: -------------------------------------------------------------------------------- 1 | import { useOptimistic, useRef, useState } from "react"; 2 | import { useFormStatus } from "react-dom"; 3 | 4 | const Button = () => { 5 | const { pending } = useFormStatus(); 6 | 7 | return ( 8 | 15 | ); 16 | }; 17 | const MessageForm = ({ sendMessage, addOptimisticMessage }) => { 18 | const formRef = useRef(); 19 | 20 | const formAction = async (formData) => { 21 | addOptimisticMessage(formData.get("message")); 22 | await sendMessage(formData); 23 | formRef.current.reset(); 24 | }; 25 | 26 | return ( 27 |
32 | 38 |
16 | 17 | ); 18 | }; 19 | 20 | const Thread = () => { 21 | return ( 22 |
23 | 24 |
25 | Message text 26 | 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default function MessageBox() { 33 | return ; 34 | } 35 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import App from "./App.jsx"; 3 | import "./index.css"; 4 | 5 | ReactDOM.createRoot(document.getElementById("root")).render(); 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------