diff --git a/Chap5/components/package-lock.json b/Chap5/components/package-lock.json
index 7e35d05..5446a6f 100644
--- a/Chap5/components/package-lock.json
+++ b/Chap5/components/package-lock.json
@@ -10,8 +10,8 @@
"dependencies": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc",
- "react": "19.0.0-rc-5dcb0097-20240918",
- "react-dom": "19.0.0-rc-5dcb0097-20240918"
+ "react": "19.0.0-rc-e4953922-20240919",
+ "react-dom": "19.0.0-rc-e4953922-20240919"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
@@ -2192,22 +2192,22 @@
]
},
"node_modules/react": {
- "version": "19.0.0-rc-5dcb0097-20240918",
- "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc-5dcb0097-20240918.tgz",
- "integrity": "sha512-kpRDycbFGXguigJtTLzT67L6FJjWUJYLru40Yk/7GSYDPy2hAfdLVXwfjczfRrnKVyIO76EVbnlBuR+zNX0bdQ==",
+ "version": "19.0.0-rc-e4953922-20240919",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc-e4953922-20240919.tgz",
+ "integrity": "sha512-4CkSUGAHyU1WJ5w90eDna6fAMDwv2irJCLl3dne3DjuxWgdPgtBjdUpSNn4mb1FZUyiJBh7RiNB57G4YjPkDkQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.0.0-rc-5dcb0097-20240918",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc-5dcb0097-20240918.tgz",
- "integrity": "sha512-QV5mYzXL8okN43EIxsMhjOMi6AP+mkZBbs3nmMcIKp3RbflINO/lUIdHnGsSm6Pc/Mc34QXCHy8RaN1DZ5o8fw==",
+ "version": "19.0.0-rc-e4953922-20240919",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0-rc-e4953922-20240919.tgz",
+ "integrity": "sha512-E8jZqdTPvWQ4SRH29nKwwMUmlnyYdx1YPWDBXfeEC5LL92Tq7qQC1Tjh75Zr1zgNsxp1c5YpyWyQTYmpeIdXQg==",
"dependencies": {
- "scheduler": "0.25.0-rc-5dcb0097-20240918"
+ "scheduler": "0.25.0-rc-e4953922-20240919"
},
"peerDependencies": {
- "react": "19.0.0-rc-5dcb0097-20240918"
+ "react": "19.0.0-rc-e4953922-20240919"
}
},
"node_modules/resolve-from": {
@@ -2288,9 +2288,9 @@
}
},
"node_modules/scheduler": {
- "version": "0.25.0-rc-5dcb0097-20240918",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-5dcb0097-20240918.tgz",
- "integrity": "sha512-Sr3CaLDHJIA1p7wzNF8EyZ/JzetrV21S0sY6byC3AhREQMoEuKol8BSmXM5gd8Aoxo92uW9WCjWL0hwdb2DtqQ=="
+ "version": "0.25.0-rc-e4953922-20240919",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-e4953922-20240919.tgz",
+ "integrity": "sha512-6l7cfQOmb5n350VJp8/CIejjEqB/1niPiwsooAHcH81ia/rMbZrzLChl14FOS6tJr0H+9P9rvgnAA6/pFHj2Mg=="
},
"node_modules/semver": {
"version": "7.6.3",
diff --git a/Chap5/components/package.json b/Chap5/components/package.json
index da5883c..5861625 100644
--- a/Chap5/components/package.json
+++ b/Chap5/components/package.json
@@ -12,8 +12,8 @@
"dependencies": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc",
- "react": "19.0.0-rc-5dcb0097-20240918",
- "react-dom": "19.0.0-rc-5dcb0097-20240918"
+ "react": "19.0.0-rc-e4953922-20240919",
+ "react-dom": "19.0.0-rc-e4953922-20240919"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
diff --git a/Chap5/components/src/App.tsx b/Chap5/components/src/App.tsx
index 63f78fa..e2acd69 100644
--- a/Chap5/components/src/App.tsx
+++ b/Chap5/components/src/App.tsx
@@ -4,6 +4,7 @@ import Home, { UserType } from "./Home";
import DeferredValue from "./DeferredValue";
import Transitioner from "./Transitioner";
import UserForm from "./UserForm";
+import { OptimisticMessages } from "./OptimisticMessages";
async function fetchData(url: string) {
const response = await fetch(url);
@@ -26,7 +27,7 @@ function App() {
return (
<>
-
+
>
);
}
diff --git a/Chap5/components/src/MessagesData.ts b/Chap5/components/src/MessagesData.ts
new file mode 100644
index 0000000..2a177f8
--- /dev/null
+++ b/Chap5/components/src/MessagesData.ts
@@ -0,0 +1,6 @@
+export type Message = { id: number; message: string };
+
+export const messagesOnApi: Message[] = [
+ { id: 1, message: "first" },
+ { id: 2, message: "second" },
+];
diff --git a/Chap5/components/src/OptimisticMessages.tsx b/Chap5/components/src/OptimisticMessages.tsx
new file mode 100644
index 0000000..17fadcf
--- /dev/null
+++ b/Chap5/components/src/OptimisticMessages.tsx
@@ -0,0 +1,83 @@
+import {
+ useOptimistic,
+ useState,
+ MouseEvent,
+ ChangeEvent,
+ useTransition,
+ useEffect,
+} from "react";
+import { Message, messagesOnApi } from "./MessagesData";
+
+export function OptimisticMessages() {
+ const [messagesRetrievedFromApi, setMessagesRetrievedFromApi] = useState<
+ Message[]
+ >([]);
+ const [message, setMessage] = useState("");
+ const [optimisticMessages, addOptimisticMessage] = useOptimistic<
+ Message[],
+ string
+ >(messagesRetrievedFromApi, (currentMessages, message) => {
+ const finalMessages = [
+ ...currentMessages,
+ { id: getNextId(currentMessages), message },
+ ];
+ return finalMessages;
+ });
+ const [_isPending, startTransition] = useTransition();
+
+ useEffect(() => {
+ const data = JSON.parse(JSON.stringify(messagesOnApi));
+ setMessagesRetrievedFromApi(data);
+ }, []);
+
+ const addMessage = async (message: string) => {
+ addOptimisticMessage(message);
+ await new Promise((res) =>
+ setTimeout(() => {
+ messagesOnApi.push({
+ id: getNextId(messagesOnApi),
+ message,
+ });
+ res(null);
+ }, 2000)
+ );
+
+ const data = JSON.parse(JSON.stringify(messagesOnApi));
+ setMessagesRetrievedFromApi(data);
+ setMessage("");
+ };
+
+ const onChange = (e: ChangeEvent) => {
+ e.preventDefault();
+ setMessage(e.target.value);
+ };
+
+ const onClick = async (e: MouseEvent) => {
+ e.preventDefault();
+
+ startTransition(() => {
+ addMessage(message);
+ });
+ };
+
+ return (
+
+
+
+
+ {optimisticMessages.map((m, i) => (
+ - {m.message}
+ ))}
+
+
+ );
+}
+
+function getNextId(currentMessages: Message[]) {
+ let id = 0;
+ for (let i = 0; i < currentMessages.length; i++) {
+ if (currentMessages[i].id > id) id = currentMessages[i].id;
+ }
+ id += 1;
+ return id;
+}
diff --git a/Chap5/components/src/SubmitButton.tsx b/Chap5/components/src/SubmitButton.tsx
new file mode 100644
index 0000000..291cff4
--- /dev/null
+++ b/Chap5/components/src/SubmitButton.tsx
@@ -0,0 +1,11 @@
+import { useFormStatus } from "react-dom";
+
+export default function SubmitButton() {
+ const { pending } = useFormStatus();
+
+ return (
+
+ );
+}
diff --git a/Chap5/components/src/UserForm.tsx b/Chap5/components/src/UserForm.tsx
index 1be7355..291d82f 100644
--- a/Chap5/components/src/UserForm.tsx
+++ b/Chap5/components/src/UserForm.tsx
@@ -1,6 +1,7 @@
import { useActionState } from "react";
import { UserType } from "./Home";
import { users } from "./UserData";
+import SubmitButton from "./SubmitButton";
export default function UserForm() {
const [_formState, action, isPending] = useActionState(
@@ -83,9 +84,7 @@ export default function UserForm() {
-
+
{JSON.stringify(users)}
);