🐥

Remix入門: loader()とは?

2024/04/24に公開

Remix公式のINDIE STCKで説明します。

https://github.com/remix-run/indie-stack

インストールはこのコマンドです。

npx create-remix@latest --template remix-run/indie-stack

ブログの記事ページです。

ディレクトリのroutes/notes/hogehogeがURLになります。

まずはファイルのルーティングからおさえていきますね。

これがファイル一覧です。

notes.がつくファイルが4つありますが、関連をもっています。

親のコンポーネントからOutletで子コンポーネントを呼び出します。呼び出したときにファイル名がURLになります。今回はブログ記事の単体ページなので$noteIDです。このドル記号をつけることで、記事のIDが動的にURLになります。

わかりやすく今回みていく記事の単体のページに緑色を付けます。

Reactをちょっと触ってる人はわかるかも知れません。コンポーネントがページになります。コンポーネントにURLを持たせるのがRemix流です。こまかく機能を管理できるので開発しやすいんだよね。

中身は、見出しと本文と削除ボタンのみなのでシンプルな構成です。

    <div className="bg-green-500">
      <h3 className="text-2xl font-bold">{data.note.title}</h3>
      <p className="py-6">{data.note.body}</p>
      <hr className="my-4" />
      <Form method="post">
        <button
          type="submit"
          className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 focus:bg-blue-400"
        >
          Delete
        </button>
      </Form>
    </div>

ブログの単ページのコード全文

app\routes\notes.$noteId.tsx
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import {
  Form,
  isRouteErrorResponse,
  useLoaderData,
  useRouteError,
} from "@remix-run/react";
import invariant from "tiny-invariant";

import { deleteNote, getNote } from "~/models/note.server";
import { requireUserId } from "~/session.server";

export const loader = async ({ params, request }: LoaderFunctionArgs) => {
  const userId = await requireUserId(request);
  invariant(params.noteId, "noteId not found");

  const note = await getNote({ id: params.noteId, userId });
  if (!note) {
    throw new Response("Not Found", { status: 404 });
  }
  return json({ note });
};

export const action = async ({ params, request }: ActionFunctionArgs) => {
  const userId = await requireUserId(request);
  invariant(params.noteId, "noteId not found");

  await deleteNote({ id: params.noteId, userId });

  return redirect("/notes");
};

export default function NoteDetailsPage() {
  const data = useLoaderData<typeof loader>();

  return (
    <div className="bg-green-500">
      <h3 className="text-2xl font-bold">{data.note.title}</h3>
      <p className="py-6">{data.note.body}</p>
      <hr className="my-4" />
      <Form method="post">
        <button
          type="submit"
          className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 focus:bg-blue-400"
        >
          Delete
        </button>
      </Form>
    </div>
  );
}

export function ErrorBoundary() {
  const error = useRouteError();

  if (error instanceof Error) {
    return <div>An unexpected error occurred: {error.message}</div>;
  }

  if (!isRouteErrorResponse(error)) {
    return <h1>Unknown Error</h1>;
  }

  if (error.status === 404) {
    return <div>Note not found</div>;
  }

  return <div>An unexpected error occurred: {error.statusText}</div>;
}


コードの気になるところ解説

コードを俯瞰して見てみよう

インポートを含めた全体の機能一覧です。

こうみるとシンプルな構成です。
上から

  • loader()
  • action()
  • コンポーネント
  • ErrorBoundary()

loader()とaction()とは?

サーバーサイド処理を記述します。
サーバーサイドの処理をTypeScriptで書きます。

この2個の関数loader()とaction()がRemixのすべてと言っても過言ではありません。Reactにサーバー処理をつけてチューニングしたのがRemixです。機能は少なめです。

コンポーネントとは?

フロントエンドを記述します。Reactの部分です。
Formを呼ぶと、サーバーサイド処理が走ります。
なのでサーバーサイドにfetchを書いておいてコンポーネントでsubmitするとサーバーサイド側にデータが飛んでサーバーサイド側でFecthします。

つまり、外部APIを使う場合はこうなります。

ブラウザ→サーバー→外部API

え?遠回りになってるって?

実際に遠回りになっています。

遠回りしないで

ブラウザ→外部API

にしたい場合は、clientLoaderが用意されてます。

https://remix.run/docs/en/main/route/loader

https://remix.run/docs/en/main/route/action

https://remix.run/docs/en/main/route/client-loader

ErrorBoundary()とは?

エラーなった時の処理を書く場所。

おわり

Remixは慣れるまでドキュメント片手にやっていくのおすすめします。

Discussion