From cb41a8643447cbc65b8f8c02d14923e1d6006f90 Mon Sep 17 00:00:00 2001 From: David Choi Date: Sun, 22 Sep 2024 11:12:10 -0400 Subject: [PATCH] add new OptimisticMessages component --- Chap5/components/package-lock.json | 26 +++---- Chap5/components/package.json | 4 +- Chap5/components/src/App.tsx | 3 +- Chap5/components/src/MessagesData.ts | 6 ++ Chap5/components/src/OptimisticMessages.tsx | 83 +++++++++++++++++++++ Chap5/components/src/SubmitButton.tsx | 11 +++ Chap5/components/src/UserForm.tsx | 5 +- 7 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 Chap5/components/src/MessagesData.ts create mode 100644 Chap5/components/src/OptimisticMessages.tsx create mode 100644 Chap5/components/src/SubmitButton.tsx 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)} );