2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RuruCun個人開発Advent Calendar 2023

Day 4

Remixのチュートリアルで入門してみる Part 1

Posted at

参考

下記のチュートリアルを参考にRemixを体感してみます。
https://remix.run/docs/en/main/start/tutorial

ZennのScrapでコメント形式にまとめたものを記事にしたものです。
https://zenn.dev/rurucun/scraps/d5d6e9c14ccb8d

テンプレートから雛形を作ります

下記のcreate-remix@latestからひな形を作成します。

 npx create-remix@latest --template remix-run/remix/templates/remix-tutorial


 remix   v2.3.1 💿 Let's build a better website...

   dir   Where should we create your new project?
         ./my-remix-app

      ◼  Template: Using remix-run/remix/templates/remix-tutorial...
      ✔  Template copied

   git   Initialize a new git repository?
         Yes

  deps   Install dependencies with npm?
         Yes

      ✔  Dependencies installed

      ✔  Git initialized

  done   That's it!

         Enter your project directory using cd ./my-remix-app
         Check out README.md for development and deploy instructions.

         Join the community at https://rmx.as/discord

下記で、開発環境を立ち上げます。

cd remix-tutorial
npm run dev

The Root Route

app/root.tsxRootRouteと呼び、UIで最初にレンダリングサれるコンポーネント。
通常はページのグローバルレイアウトを含みます。

linksへスタイルを追加する

下記のコードで、CSSをJSに直接インポートできる。

appstylesを追加する。

root.tsx
+import type { LinksFunction } from "@remix-run/node";
// existing imports

+import appStylesHref from "./app.css";

+export const links: LinksFunction = () => [
+  { rel: "stylesheet", href: appStylesHref },
+];

連絡先のルーティングを作成する。

サイドバーの/contacts/1 /contacts/2を動くように修正します。

Remixファイルルーティングでは、.はURLに/を作り、$はセグメントを動的にする。

下記は、/contacts/123 /contacts/abcなどに対応するファイルを作成することになります。

mkdir app/routes
touch app/routes/contacts.\$contactId.tsx

ComponentUIを作成します。

app/routes/contacts.$contactId.tsx
import { Form } from "@remix-run/react";
import type { FunctionComponent } from "react";

import type { ContactRecord } from "../data";

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
          alt={`${contact.first} ${contact.last} avatar`}
          key={contact.avatar}
          src={contact.avatar}
        />
      </div>

      <div>
        <h1>
          {contact.first || contact.last ? (
            <>
              {contact.first} {contact.last}
            </>
          ) : (
            <i>No Name</i>
          )}{" "}
          <Favorite contact={contact} />
        </h1>

        {contact.twitter ? (
          <p>
            <a
              href={`https://twitter.com/${contact.twitter}`}
            >
              {contact.twitter}
            </a>
          </p>
        ) : null}

        {contact.notes ? <p>{contact.notes}</p> : null}

        <div>
          <Form action="edit">
            <button type="submit">Edit</button>
          </Form>

          <Form
            action="destroy"
            method="post"
            onSubmit={(event) => {
              const response = confirm(
                "Please confirm you want to delete this record."
              );
              if (!response) {
                event.preventDefault();
              }
            }}
          >
            <button type="submit">Delete</button>
          </Form>
        </div>
      </div>
    </div>
  );
}

const Favorite: FunctionComponent<{
  contact: Pick<ContactRecord, "favorite">;
}> = ({ contact }) => {
  const favorite = contact.favorite;

  return (
    <Form method="post">
      <button
        aria-label={
          favorite
            ? "Remove from favorites"
            : "Add to favorites"
        }
        name="favorite"
        value={favorite ? "false" : "true"}
      >
        {favorite ? "" : ""}
      </button>
    </Form>
  );
};

上記を追加しても、まだコンテンツが表示されません。

ネストされたルーティングとアウトレット

RemixはReact Routerの上に構築されているので、ネストされたルーティングをサポートしています。

子ルートが親レイアウトの内部でレンダリングされるようにするには、親でアウトレットをレンダリングする必要があります。

app/root.tsxを開き、内部でアウトレットをレンダリングします。

をRootへ追加する。

app/root.tsx
// existing imports
import {
  Form,
  Links,
  LiveReload,
  Meta,
+  Outlet,
  Scripts,
  ScrollRestoration,
} from "@remix-run/react";

// existing imports & code

export default function App() {
  return (
    <html lang="en">
      {/* other elements */}
      <body>
        <div id="sidebar">{/* other elements */}</div>
+        <div id="detail">
+         <Outlet />
+        </div>
        {/* other elements */}
      </body>
    </html>
  );
}

rootへを追加したことで、routes/contacts.$contactId.tsxの内容が表示されます。

Client Side Routing

現状の実装では、サイドバーのリンクをクリックすると、URLのフルドキュメントリクエストを行っています。

ClientSideRoutingを実装することによって、アプリはサーバーにリクエストすることなくURLを更新できます。

を に変換します。

app/root.tsx
// existing imports
import {
+  Link,
} from "@remix-run/react";

export default function App() {
  return (
      {/* 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 */}
  );
}

データの読み込み

URLセグメント、レイアウト、データは、多くの場合、3重結合するパターンが多い。

URL Segment Component Data
/ list of contacts
contacts/:contactId individual contact

これらの3つを自然に結合させるために、Remixはルートコンポーネントにデータを簡単に取り込むためのデータ規約を持っています。

データをロードするために、loaderuseLoaderDataという2つのAPIを使います。まず、ルートルートにloader関数を作成してエクスポートし、データをレンダリングします。

app/root.tsx

+ import { json } from "@remix-run/node";
import {
+  useLoaderData,
} from "@remix-run/react";

// 用意されているデータです。
import { getContacts } from "./data";

+ export const loader = async () => {
+  const contacts = await getContacts();
+ return json({ contacts });
+};

export default function App() {
+  const { contacts } = useLoaderData();

  return (
    <html lang="en">
      {/* other elements */}
      <body>
        <div id="sidebar">
          {/* other elements */}
          <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>
+                      ) : null}
+                    </Link>
+                  </li>
+                ))}
+              </ul>
+            ) : (
+              <p>
+                <i>No contacts</i>
+             </p>
            )}
          </nav>
        </div>
        {/* other elements */}
      </body>
    </html>
  );
}

サイドバーにデータが表示されるようになります。

型推論

次のようにして、簡単なアノテーションを追加して、データに関する型推論を取得できますtypeof loader。

export default function App() {
+  const { contacts } = useLoaderData<typeof loader>();

  // existing code
}

LoaderのURLパラメーター

loaderの処理でparamsからcontactIdを取得し、データを参照するように修正します。

tsx app/routes/contacts.$contactId.tsx
+ import { json } from "@remix-run/node";
+ import { Form, useLoaderData } from "@remix-run/react";
// existing imports

+ import { getContact } from "../data";

+ export const loader = async ({ params }) => {
+  const contact = await getContact(params.contactId);
+  return json({ contact });
+};

export default function Contact() {
+  const { contact } = useLoaderData<typeof loader>();

  // existing code
}

paramsのcontactIdからローカルのjsonのデータを表示するようになったので、サイドバーで各ページへ移動すると、それぞれの名前と画像が表示されるようになりました。

パラメータの検証と404

ユーザーが見つからない場合の処理を追加する。

app/routes/contacts.$contactId.tsx
+ import type { LoaderFunctionArgs } from "@remix-run/node";
+ import invariant from "tiny-invariant";

+ export const loader = async ({
+   params,
+ }: LoaderFunctionArgs) => {
+   invariant(params.contactId, "Missing contactId param");
  const contact = await getContact(params.contactId);
+  if (!contact) {
+    throw new Response("Not Found", { status: 404 });
+  }
  return json({ contact });
};
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?