npm create vite@latest name-of-your-project -- --template react
# follow prompts
cd <your new project directory>
npm install react-router-dom localforage match-sorter sort-by
npm run dev
result:
VITE v3.0.7 ready in 175 ms
➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import './index.css'
const router = createBrowserRouter([
{
path: "/",
element: <div>Hello world!</div>,
},
]);
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
root.jsx
export default function Root() {
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"
type="search"
name="q"
/>
<div
id="search-spinner"
aria-hidden
hidden={true}
/>
<div
className="sr-only"
aria-live="polite"
></div>
</form>
<form method="post">
<button type="submit">New</button>
</form>
</div>
<nav>
<ul>
<li>
<a href={`/contacts/1`}>Your Name</a>
</li>
<li>
<a href={`/contacts/2`}>Your Friend</a>
</li>
</ul>
</nav>
</div>
<div id="detail"></div>
</>
);
}
main.jsx
/* import 추가 */
import Root from "./routes/root";
/* .... */
/* element 대상을 Root로 변경 */
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
},
]);
/* .... */
사이드바에 있는 버튼 클릭시 404에러 출력
에러 확인시 리액트 라우터는 이를 확인해 오류화면을 출력함
에러 페이지를 출력하는 컴포넌트를 만들고 이를 router 객체의 errorElement 속성으로 추가해 에러 화면을 커스텀할 수 있음
src/error-page.jsx
import { useRouteError } from "react-router-dom";
export default function ErrorPage() {
const error = useRouteError();
console.error(error);
return (
<div id="error-page">
<h1>Oops!</h1>
<p>Sorry, an unexpected error has occurred.</p>
<p>
<i>{error.statusText || error.message}</i>
</p>
</div>
);
}
main.jsx
/* ... */
import ErrorPage from "./error-page";
/* ... */
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
},
]);
/* ... */
contact.jsx
import { Form } from "react-router-dom";
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,
};
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>
);
}
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>
);
}
main.jsx
/* existing imports */
import Contact from "./routes/contact";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
},
{
path: "contacts/:contactId",
element: <Contact />,
},
]);
/* existing code */
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
<Outlet>
컴포넌트를 추가해 라우팅에 따른 다른 child component가 올 수 있음을 알려야함root.jsx
import { Outlet } from "react-router-dom";
export default function Root() {
return (
<>
{/* all the other elements */}
<div id="detail">
<Outlet />
</div>
</>
);
}
root.jsx
import { Outlet, Link } from "react-router-dom";
export default function Root() {
return (
<>
<div id="sidebar">
{/* other elements */}
<nav>
<ul>
<li>
<Link to={`contacts/1`}>Your Name</Link>
</li>
<li>
<Link to={`contacts/2`}>Your Friend</Link>
</li>
</ul>
</nav>
{/* other elements */}
</div>
</>
);
}
root.jsx
import {
Outlet,
Link,
useLoaderData,
} from "react-router-dom";
import { getContacts } from "../contacts";
export async function loader() {
const contacts = await getContacts();
return { contacts };
}
export default function Root() {
const { contacts } = useLoaderData();
return (
<>
<div id="sidebar">
<h1>React Router Contacts</h1>
{/* other code */}
<nav>
{contacts.length ? (
<ul>
{contacts.map((contact) => (
<li key={contact.id}>
<Link to={`contacts/${contact.id}`}>
{contact.first || contact.last ? (
<>
{contact.first} {contact.last}
</>
) : (
<i>No Name</i>
)}{" "}
{contact.favorite && <span>★</span>}
</Link>
</li>
))}
</ul>
) : (
<p>
<i>No contacts</i>
</p>
)}
</nav>
{/* other code */}
</div>
</>
);
}
/* other code */
main.jsx
/* other imports */
import Root, { loader as rootLoader } from "./routes/root";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
root.jsx
import {
Outlet,
Link,
useLoaderData,
Form,
} from "react-router-dom";
import { getContacts, createContact } from "../contacts";
export async function action() {
const contact = await createContact();
return { contact };
}
/* other code */
export default function Root() {
const { contacts } = useLoaderData();
return (
<>
<div id="sidebar">
<h1>React Router Contacts</h1>
<div>
{/* other code */}
<Form method="post">
<button type="submit">New</button>
</Form>
</div>
{/* other code */}
</div>
</>
);
}
main.jsx
/* other imports */
import Root, {
loader as rootLoader,
action as rootAction,
} from "./routes/root";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
[
{
path: "contacts/:contactId",
element: <Contact />,
},
];
contact.jsx
import { Form, useLoaderData } from "react-router-dom";
import { getContact } from "../contacts";
export async function loader({ params }) {
return getContact(params.contactId);
}
export default function Contact() {
const contact = useLoaderData();
// existing code
}
main.jsx
/* existing code */
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,
},
],
},
]);
/* existing code */