Explorar o código

React Router WIP.

Frederic G. MARAND %!s(int64=2) %!d(string=hai) anos
pai
achega
66f16d397b

+ 26 - 14
router-tuto/src/main.jsx

@@ -1,29 +1,41 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import {createBrowserRouter, RouterProvider} from 'react-router-dom';
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { createBrowserRouter, RouterProvider } from "react-router-dom";
 
-import './index.css'
-import Root, {loader as rootLoader} from './routes/root';
-import Contact from "./routes/contact.jsx";
+import "./index.css";
+import Root, {
+  action as rootAction,
+  loader as rootLoader,
+} from "./routes/root";
+import Contact, { loader as contactLoader } from "./routes/contact.jsx";
+import EditContact, { action as editAction } from "./routes/edit.jsx";
 import ErrorPage from "./error-page.jsx";
 
 const router = createBrowserRouter([
   {
     path: "/",
-    element: <Root/>,
-    errorElement: <ErrorPage/>,
+    element: <Root />,
+    errorElement: <ErrorPage />,
     loader: rootLoader,
+    action: rootAction,
     children: [
       {
         path: "contacts/:contactId",
-        element: <Contact/>,
+        element: <Contact />,
+        loader: contactLoader,
       },
-    ]
+      {
+        path: "contacts/:contactId/edit",
+        element: <EditContact />,
+        loader: contactLoader,
+        action: editAction,
+      },
+    ],
   },
 ]);
 
-ReactDOM.createRoot(document.getElementById('root')).render(
+ReactDOM.createRoot(document.getElementById("root")).render(
   <React.StrictMode>
-    <RouterProvider router={router}/>
-  </React.StrictMode>,
-)
+    <RouterProvider router={router} />
+  </React.StrictMode>
+);

+ 76 - 78
router-tuto/src/routes/contact.jsx

@@ -1,89 +1,87 @@
-import {Form} from "react-router-dom";
+import {Form, useLoaderData} from "react-router-dom";
+import {getContact} from "../contacts.js";
+
+export async function loader({params}) {
+    const contact = await getContact(params.contactId);
+    console.log("loader, contact", contact);
+    return  { contact };
+}
 
 export default function Contact() {
-  const contact = {
-    first: "Your",
-    last: "Name",
-    avatar: "https://placekitten.com/g/200/200",
-    twitter: "your_handle",
-    notes: "Some notes",
-    favorite: true,
-  };
+    let { contact } = useLoaderData();
+    // const contact = {
+    //     first: "Your",
+    //     last: "Name",
+    //     avatar: "https://placekitten.com/g/200/200",
+    //     twitter: "your_handle",
+    //     notes: "Some notes",
+    //     favorite: true,
+    // };
 
-  return(
-    <div id="contact">
-      <div>
-        <img
-          key={contact.avatar}
-          src={contact.avatar || null}
-        />
-      </div>
+    return (
+        <div id="contact">
+            <div>
+                <img key={contact.avatar} src={contact.avatar || null}/>
+            </div>
 
-      <div>
-        <h1>
-          {contact.first || contact.last ? (
-            <>
-              {contact.first} {contact.last}
-            </>
-          ) : (
-            <i>No Name</i>
-          )}{" "}
-          <Favorite contact={contact}/>
-        </h1>
+            <div>
+                <h1>
+                    {contact.first || contact.last ? (
+                        <>
+                            {contact.first} {contact.last}
+                        </>
+                    ) : (
+                        <i>No Name</i>
+                    )}{" "}
+                    <Favorite contact={contact}/>
+                </h1>
 
-        {contact.twitter && (
-          <p>
-            <a
-              target="_blank"
-              href={`https://twitter.com/${contact.twitter}`}
-            >
-              {contact.twitter}
-            </a>
-          </p>
-        )}
-        {contact.notes && <p>{contact.notes}</p>}
+                {contact.twitter && (
+                    <p>
+                        <a target="_blank" href={`https://twitter.com/${contact.twitter}`}>
+                            {contact.twitter}
+                        </a>
+                    </p>
+                )}
+                {contact.notes && <p>{contact.notes}</p>}
 
-        <div>
-          <Form action="edit">
-            <button type="submit">Edit</button>
-          </Form>
-          <Form
-            method="post"
-            action="destroy"
-            onSubmit={(event) => {
-              if (
-                !confirm(
-                  "Please confirm you want to delete this record."
-                )
-              ) {
-                event.preventDefault();
-              }
-            }}
-          >
-            <button type="submit">Delete</button>
-          </Form>
+                <div>
+                    <Form action="edit">
+                        <button type="submit">Edit</button>
+                    </Form>
+                    <Form
+                        method="post"
+                        action="destroy"
+                        onSubmit={(event) => {
+                            if (!confirm("Please confirm you want to delete this record.")) {
+                                event.preventDefault();
+                            }
+                        }}
+                    >
+                        <button type="submit">Delete</button>
+                    </Form>
+                </div>
+            </div>
         </div>
-      </div>
-    </div>
-  )
+    );
 };
 
 function Favorite({contact}) {
-  // yes, this is a `let` for later
-  let favorite = contact.favorite;
-  return (
-    <Form method="post">
-      <button
-        name="favorite"
-        value={favorite ? "false" : "true"}
-        aria-label={
-          favorite
-            ? "Remove from favorites"
-            : "Add to favorites"
-        }
-      >
-        {favorite ? "★" : "☆"}
-      </button>
-    </Form>
-  );
+    // yes, this is a `let` for later
+    let favorite = contact.favorite;
+    return (
+        <Form method="post">
+            <button
+                name="favorite"
+                value={favorite ? "false" : "true"}
+                aria-label={
+                    favorite
+                        ? "Remove from favorites"
+                        : "Add to favorites"
+                }
+            >
+                {favorite ? "★" : "☆"}
+            </button>
+        </Form>
+    );
 }

+ 62 - 0
router-tuto/src/routes/edit.jsx

@@ -0,0 +1,62 @@
+import {Form, redirect, useLoaderData} from "react-router-dom";
+import { updateContact} from "../contacts.js";
+
+export async function action({ request, params }) {
+    const formData = await request.formData();
+    const updates = Object.fromEntries(formData);
+    await updateContact(params.contactId, updates);
+    return redirect(`/contacts/${params.contactId}`);
+}
+
+export default function EditContact() {
+    const { contact } = useLoaderData();
+
+    return (
+        <Form method="post" id="contact-form">
+            <p>
+                <span>Name</span>
+                <input
+                    placeholder="First"
+                    aria-label="First name"
+                    type="text"
+                    name="first"
+                    defaultValue={contact.first}
+                />
+                <input
+                    placeholder="Last"
+                    aria-label="Last name"
+                    type="text"
+                    name="last"
+                    defaultValue={contact.last}
+                />
+            </p>
+            <label>
+                <span>Twitter</span>
+                <input
+                    type="text"
+                    name="twitter"
+                    placeholder="@jack"
+                    defaultValue={contact.twitter}
+                />
+            </label>
+            <label>
+                <span>Avatar URL</span>
+                <input
+                    placeholder="https://example.com/avatar.jpg"
+                    aria-label="Avatar URL"
+                    type="text"
+                    name="avatar"
+                    defaultValue={contact.avatar}
+                />
+            </label>
+            <label>
+                <span>Notes</span>
+                <textarea name="notes" defaultValue={contact.notes} rows={6}/>
+            </label>
+            <p>
+                <button type="submit">Save</button>
+                <button type="button">Cancel</button>
+            </p>
+        </Form>
+    );
+}

+ 29 - 16
router-tuto/src/routes/root.jsx

@@ -1,26 +1,39 @@
-import {Link, Outlet, useLoaderData} from "react-router-dom";
-import {getContacts} from "../contacts.js";
+import { Form, Link, Outlet, redirect, useLoaderData } from "react-router-dom";
+import { createContact, getContacts } from "../contacts.js";
+
+export async function action() {
+  const contact = await createContact();
+  console.log("action: contact", contact);
+  return redirect(`/contacts/${contact.id}/edit`);
+}
 
 export async function loader() {
   const contacts = await getContacts();
-  return {contacts};
+  console.log("loader, contacts", contacts.length, contacts);
+  return { contacts };
 }
 
-export default function root() {
-  const contacts = useLoaderData();
+export default function Root() {
+  const { contacts } = useLoaderData();
   return (
     <>
       <div id="sidebar">
         <h1>React Router contacts</h1>
         <div>
           <form id="search-form" role="search">
-            <input id="q" aria-label="Search contacts" placeholder="search" name="q" type="search"/>
-            <div id="search-spinner" aria-hidden hidden={true}/>
+            <input
+              id="q"
+              aria-label="Search contacts"
+              placeholder="search"
+              name="q"
+              type="search"
+            />
+            <div id="search-spinner" aria-hidden hidden={true} />
             <div className="sr-only" aria-live="polite"></div>
           </form>
-          <form method="post">
+          <Form method="post">
             <button type="submit">New</button>
-          </form>
+          </Form>
         </div>
         <nav>
           {contacts.length ? (
@@ -30,7 +43,7 @@ export default function root() {
                   <Link to={`contacts/${contact.id}`}>
                     {contact.first || contact.last ? (
                       <>
-                        {contact.first} {contact.last}
+                        F: {contact.first} L: {contact.last}
                       </>
                     ) : (
                       <i>No name</i>
@@ -38,18 +51,18 @@ export default function root() {
                     {contact.favorite && <span>*</span>}
                   </Link>
                 </li>
-              ))
-              }
+              ))}
             </ul>
           ) : (
-            <p><i>No contacts</i></p>
+            <p>
+              <i>No contacts</i>
+            </p>
           )}
         </nav>
       </div>
       <div id="detail">
-        <Outlet/>
+        <Outlet />
       </div>
     </>
-  )
-    ;
+  );
 };