英文:
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: "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
}
]
}
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 "react-router-dom"
import { getContacts, createContact } from "../contacts"
export async function action() {
const contact = await createContact();
console.log("Contact made")
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("contacts");
let contact = contacts.find(contact => 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 "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();
return (
<div id="contact">
<div>
<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}
/>
</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>
);
}
答案1
得分: 2
在路由路径参数中存在一些微妙但有害的大小写问题。
Contacts
组件的路由路径参数声明为 contactID
。
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactID", // <-- "contactID"
element: <Contact />,
loader: contactLoader,
},
],
},
]);
联系加载器在引用 contactid
路径参数。
export async function loader({ params }) {
const contact = await getContact(params.contactid); // <-- "contactid"
return { contact };
}
因此,加载器函数无法找到匹配项,并将 null
返回给 Contact
组件。在尝试访问空引用的属性时,UI 中会引发错误。
任何有效的 JavaScript 标识符都可以作为路由路径参数的名称,但它们应该保持一致。在 JavaScript 中,变量名的大小写很重要。 变量名的通用约定是使用驼峰命名法,例如 contactId
。
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
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: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactID", // <-- "contactID"
element: <Contact />,
loader: contactLoader,
},
],
},
]);
The contact loader is referencing a contactid
path parameter.
export async function loader({ params }) {
const contact = await getContact(params.contactid); // <-- "contactid"
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: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
},
],
},
]);
export async function loader({ params }) {
const contact = await getContact(params.contactId);
return { contact };
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论