Going through the react-router tutorial and rather than using default or null values when loading an empty object, it kicks up an error

huangapple go评论71阅读模式
英文:

Going through the react-router tutorial and rather than using default or null values when loading an empty object, it kicks up an error

问题

I've been going through react-router's tutorial, and I've been following it to the letter as far as I'm aware. I'm having some issues with the url params in loaders segment.

The static contact code looks like this

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,
  }

And when it loads, it looks like this. That works just fine, however, the tutorial then tells me to change that code so that I use data that's loaded in instead. The code now looks like this

import { Form, useLoaderData } from "react-router-dom";
import { getContact } from "../contacts";

export async function loader({ params }) {
  const contact = await getContact(params.contactid);
  return { contact }
}

export default function Contact() {
  const { contact } = useLoaderData();

According to the tutorial, it should just load in an empty contact that looks like this but instead, every time I try to open one of the new contacts, it kicks up an error saying

React Router caught the following error during render TypeError: contact is null

The actual line of code this error points to is in the return segment of the contact component, which looks like this

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>

        {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>
      </div>
    </div>
  );
}

Pretty much anywhere contacts is called gets an error. So, anyone have any idea what I'm doing wrong here? To my knowledge, I've been following their guide to the letter and it seems like it should be able to handle contacts not having any data, but it's not.

These are the pieces of my code that are supposed to be working together to render a contact, or at least the pertinent parts

The router, this is the main file, the only part missing is the part where it's rendered

import * as React from "react";
import * as ReactDOM from "react-dom/client";
import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";
import "./index.css";
import Root, { loader as rootLoader, action as rootAction } from "./routes/root";
import ErrorPage from "./error-page";
import Contact, { loader as contactLoader } from "./routes/contact";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: "contacts/:contactID",
        element: <Contact />,
        loader: contactLoader
      }
    ]
  }
英文:

I've been going through react-router's tutorial, and I've been following it to the letter as far as I'm aware. I'm having some issues with the url params in loaders segment.

The static contact code looks like this

export default function Contact() {
  const contact = {
    first: &quot;Your&quot;,
    last: &quot;Name&quot;,
    avatar: &quot;https://placekitten.com/g/200/200&quot;,
    twitter: &quot;your_handle&quot;,
    notes: &quot;Some notes&quot;,
    favorite: true,
  }

And when it loads, it looks like this. That works just fine, however, the tutorial then tells me to change that code so that I use data that's loaded in instead. The code now looks like this

import { Form, useLoaderData } from &quot;react-router-dom&quot;;
import { getContact } from &quot;../contacts&quot;

export async function loader({ params }) {
  const contact = await getContact(params.contactid);
  return {contact}
}

export default function Contact() {
  const { contact } = useLoaderData();

According to the tutorial, it should just load in an empty contact that looks like this but instead, every time I try to open one of the new contacts, it kicks up an error saying

React Router caught the following error during render TypeError: contact is null

The actual line of code this error points to is in the return segment of the contact component, which looks like this

return (
    &lt;div id=&quot;contact&quot;&gt;
      &lt;div&gt;
        &lt;img
          key={contact.avatar}
          src={contact.avatar || null}
        /&gt;
      &lt;/div&gt;

      &lt;div&gt;
        &lt;h1&gt;
          {contact.first || contact.last ? (
            &lt;&gt;
              {contact.first} {contact.last}
            &lt;/&gt;
          ) : (
            &lt;i&gt;No Name&lt;/i&gt;
          )}{&quot; &quot;}
          &lt;Favorite contact={contact} /&gt;
        &lt;/h1&gt;

        {contact.twitter &amp;&amp; (
          &lt;p&gt;
            &lt;a
              target=&quot;_blank&quot;
              href={`https://twitter.com/${contact.twitter}`}
            &gt;
              {contact.twitter}
            &lt;/a&gt;
          &lt;/p&gt;
        )}

        {contact.notes &amp;&amp; &lt;p&gt;{contact.notes}&lt;/p&gt;}

        &lt;div&gt;
          &lt;Form action=&quot;edit&quot;&gt;
            &lt;button type=&quot;submit&quot;&gt;Edit&lt;/button&gt;
          &lt;/Form&gt;
          &lt;Form
            method=&quot;post&quot;
            action=&quot;destroy&quot;
            onSubmit={(event) =&gt; {
              if (
                !confirm(
                  &quot;Please confirm you want to delete this record.&quot;
                )
              ) {
                event.preventDefault();
              }
            }}
          &gt;
            &lt;button type=&quot;submit&quot;&gt;Delete&lt;/button&gt;
          &lt;/Form&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

Pretty much anywhere contacts is called gets an error. So, anyone have any idea what I'm doing wrong here? To my knowledge, I've been following their guide to the letter and it seems like it should be able to handle contacts not having any data, but it's not.

These are the pieces of my code that are supposed to be working together to render a contact, or at least the pertinent parts

The router, this is the main file, the only part missing is the part where it's rendered

import * as React from &quot;react&quot;;
import * as ReactDOM from &quot;react-dom/client&quot;;
import {
  createBrowserRouter,
  RouterProvider,
} from &quot;react-router-dom&quot;;
import &quot;./index.css&quot;;
import Root, { loader as rootLoader, action as rootAction } from &quot;./routes/root&quot;;
import ErrorPage from &quot;./error-page&quot;;
import Contact, { loader as contactLoader } from &quot;./routes/contact&quot;

const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &lt;Root /&gt;,
    errorElement: &lt;ErrorPage /&gt;,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: &quot;contacts/:contactID&quot;,
        element: &lt;Contact /&gt;,
        loader: contactLoader
      }
    ]
  }

These are the functions in the root file that are called when a new contact is made and when it needs to be displayed

import { Outlet, Link, useLoaderData, Form } from &quot;react-router-dom&quot;
import { getContacts, createContact } from &quot;../contacts&quot;

export async function action() {
  const contact = await createContact();
  console.log(&quot;Contact made&quot;)
  return {contact}
}

export async function loader(){
  const contacts = await getContacts();
  return {contacts};
}

This is the createContacts function that gets called when a contact is created, and this is the getContacts function

export async function createContact() {
  await fakeNetwork();
  let id = Math.random().toString(36).substring(2, 9);
  let contact = { id, createdAt: Date.now() };
  let contacts = await getContacts();
  contacts.unshift(contact);
  await set(contacts);
  return contact;
}

export async function getContact(id) {
  await fakeNetwork(`contact:${id}`);
  let contacts = await localforage.getItem(&quot;contacts&quot;);
  let contact = contacts.find(contact =&gt; contact.id === id);
  return contact ?? null;
}

This is the contacts.jsx file where things are currently going wrong. When a new contact is made, it's going to be empty, which I imagine is the source of the problem, but there are checks here to deal with that, or at least there are supposed to be.

import { Form, useLoaderData } from &quot;react-router-dom&quot;;
import { getContact } from &quot;../contacts&quot;

export async function loader({ params }) {
  const contact = await getContact(params.contactid);
  return { contact }
}

export default function Contact() {
  const { contact } = useLoaderData();

  return (
    &lt;div id=&quot;contact&quot;&gt;
      &lt;div&gt;
        &lt;img
	      // these next two lines are where the errors typically start, 
          // although it seems to extend down to any instance where contact
          // gets called.
          key={contact.avatar}
          src={contact.avatar || null}
        /&gt;
      &lt;/div&gt;

      &lt;div&gt;
        &lt;h1&gt;
          {contact.first || contact.last ? (
            &lt;&gt;
              {contact.first} {contact.last}
            &lt;/&gt;
          ) : (
            &lt;i&gt;No Name&lt;/i&gt;
          )}{&quot; &quot;}
          &lt;Favorite contact={contact} /&gt;
        &lt;/h1&gt;

        {contact.twitter &amp;&amp; (
          &lt;p&gt;
            &lt;a
              target=&quot;_blank&quot;
              href={`https://twitter.com/${contact.twitter}`}
            &gt;
              {contact.twitter}
            &lt;/a&gt;
          &lt;/p&gt;
        )}

        {contact.notes &amp;&amp; &lt;p&gt;{contact.notes}&lt;/p&gt;}

        &lt;div&gt;
          &lt;Form action=&quot;edit&quot;&gt;
            &lt;button type=&quot;submit&quot;&gt;Edit&lt;/button&gt;
          &lt;/Form&gt;
          &lt;Form
            method=&quot;post&quot;
            action=&quot;destroy&quot;
            onSubmit={(event) =&gt; {
              if (
                !confirm(
                  &quot;Please confirm you want to delete this record.&quot;
                )
              ) {
                event.preventDefault();
              }
            }}
          &gt;
            &lt;button type=&quot;submit&quot;&gt;Delete&lt;/button&gt;
          &lt;/Form&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

答案1

得分: 2

在路由路径参数中存在一些微妙但有害的大小写问题。

Contacts 组件的路由路径参数声明为 contactID

const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &lt;Root /&gt;,
    errorElement: &lt;ErrorPage /&gt;,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: &quot;contacts/:contactID&quot;, // &lt;-- &quot;contactID&quot;
        element: &lt;Contact /&gt;,
        loader: contactLoader,
      },
    ],
  },
]);

联系加载器在引用 contactid 路径参数。

export async function loader({ params }) {
  const contact = await getContact(params.contactid); // &lt;-- &quot;contactid&quot;
  return { contact };
}

因此,加载器函数无法找到匹配项,并将 null 返回给 Contact 组件。在尝试访问空引用的属性时,UI 中会引发错误。

任何有效的 JavaScript 标识符都可以作为路由路径参数的名称,但它们应该保持一致。在 JavaScript 中,变量名的大小写很重要。 变量名的通用约定是使用驼峰命名法,例如 contactId

const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &lt;Root /&gt;,
    errorElement: &lt;ErrorPage /&gt;,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: &quot;contacts/:contactId&quot;,
        element: &lt;Contact /&gt;,
        loader: contactLoader,
      },
    ],
  },
]);
export async function loader({ params }) {
  const contact = await getContact(params.contactId);
  return { contact };
}
英文:

There are some subtle, but detrimental, casing issues in the route path params.

The Contacts component's route path param is declared as contactID.

const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &lt;Root /&gt;,
    errorElement: &lt;ErrorPage /&gt;,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: &quot;contacts/:contactID&quot;, // &lt;-- &quot;contactID&quot;
        element: &lt;Contact /&gt;,
        loader: contactLoader,
      },
    ],
  },
]);

The contact loader is referencing a contactid path parameter.

export async function loader({ params }) {
  const contact = await getContact(params.contactid); // &lt;-- &quot;contactid&quot;
  return { contact };
}

As such, the loader function is unable to find a match and returns null to the Contact component. An error is thrown in the UI when attempting to access properties of the null reference.

Any valid Javascript identifier will work as the name of the route path parameter, but they should all be in agreement. Casing matters in variable names in Javascript. The common convention in variable names is to use camelCasing, e.g. contactId.

const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &lt;Root /&gt;,
    errorElement: &lt;ErrorPage /&gt;,
    loader: rootLoader,
    action: rootAction,
    children: [
      {
        path: &quot;contacts/:contactId&quot;,
        element: &lt;Contact /&gt;,
        loader: contactLoader,
      },
    ],
  },
]);
export async function loader({ params }) {
  const contact = await getContact(params.contactId);
  return { contact };
}

huangapple
  • 本文由 发表于 2023年6月2日 00:00:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76383760.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定