├── .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 |
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 |
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 |
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 |
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 |
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 |
40 | );
41 | };
42 |
43 | const Thread = ({ messages, sendMessage }) => {
44 | const [optimisticMessages, addOptimisticMessage] = useOptimistic(
45 | messages,
46 | (state, newMessage) => [
47 | ...state,
48 | {
49 | text: newMessage,
50 | sending: true,
51 | },
52 | ]
53 | );
54 |
55 | return (
56 |
57 |
61 | {optimisticMessages.map((message, index) => (
62 |
63 | {message.text}
64 | {message.sending && (
65 | ⌛
66 | )}
67 |
68 | ))}
69 |
70 | );
71 | };
72 |
73 | const deliverMessage = async (message) => {
74 | await new Promise((resolve) => {
75 | setTimeout(() => {
76 | resolve();
77 | }, 2000);
78 | });
79 | return message;
80 | };
81 |
82 | export default function MessageBox() {
83 | const [messages, setMessages] = useState([]);
84 |
85 | const sendMessage = async (formData) => {
86 | const sentMessage = await deliverMessage(formData.get("message"));
87 |
88 | setMessages((prevMessages) => [
89 | ...prevMessages,
90 | {
91 | text: sentMessage,
92 | },
93 | ]);
94 | };
95 |
96 | return ;
97 | }
98 |
--------------------------------------------------------------------------------
/src/components/useOptimistic/Starter.jsx:
--------------------------------------------------------------------------------
1 | const MessageForm = () => {
2 | return (
3 |
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 |
--------------------------------------------------------------------------------