0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Remixを学びたい(Step2:バックエンドとの疎通をする)

Last updated at Posted at 2025-04-29

はじめに

前回の続きとなります。
今回はバックエンド側の実装をし、フロントとの繋ぎ込みを実施します!

シリーズもの

成果物

scsho2.gif
なんのて変哲もない表示です。ただ、前回まではフロントの固定値を返却していましたが、今回はloaderを使ってバックエンドの値を元にバインドしています。

Remixのloader関数とは?

サーバーサイドでデータを準備し、コンポーネントに届けるRemixの特別な関数

loaderの3つのポイント

  1. サーバーサイドで実行される
  2. ページアクセス時に自動的に呼ばれる
  3. 準備したデータはuseLoaderDataで簡単に取得できる

シンプルな例

// サーバーサイド: データを準備
export async function loader() {
  const todos = ["Remixを学ぶ", "TODOアプリを作る"];
  return json({ todos });
}

// クライアントサイド: データを使用
export default function Page() {
  const { todos } = useLoaderData<typeof loader>();
  return <ul>{todos.map(todo => <li>{todo}</li>)}</ul>;
}

なぜloaderが便利か?

  • データ取得のロジックをサーバーに集中できる
  • コンポーネントはデータの表示に集中できる
  • 型安全にデータをやり取りできる

今回やりたいこと

ソースコード

ディレクトリ構成
~/develop/remix_sample  (feat/step2_api)$ tree app/
app/
├── entry.client.tsx
├── entry.server.tsx
├── models
│   └── todo.ts # バックエンドとフロントの共通コンポーネント
├── root.tsx
├── routes
│   └── _index.tsx  # フロント
├── services
│   └── todoService.ts # バックエンド
└── tailwind.css

4 directories, 7 files
app/services/todoService.ts
import type { Todo } from "~/models/todo";

// モックデータ
const mockTodos: Todo[] = [
	{ id: 1, title: "Remixを学ぶ", completed: false },
	{ id: 2, title: "TODOアプリを作る", completed: true },
	{ id: 3, title: "コードを理解する", completed: false },
];

export async function getTodos() {
	// APIレスポンスを模倣
	await new Promise((resolve) => setTimeout(resolve, 500));
	return mockTodos;
}

app/models/todo.ts
export type Todo = {
	id: number;
	title: string;
	completed: boolean;
};

app/routes/_index.tsx
import { json } from "@remix-run/node";
import { useLoaderData, useNavigation } from "@remix-run/react";
import type { MetaFunction } from "@remix-run/node";
import { getTodos } from "~/services/todoService";

export const meta: MetaFunction = () => {
	return [
		{ title: "TODO App" },
		{ name: "description", content: "Simple TODO App with Remix" },
	];
};

// loader(サーバーサイド)
export async function loader() {
	const todos = await getTodos();
	return json({ todos });
}

// コンポーネント(クライアントサイド)
export default function Index() {
	const { todos } = useLoaderData<typeof loader>();
	const navigation = useNavigation();
	const isLoading = navigation.state === "loading";

	return (
		<div className="max-w-2xl mx-auto p-4">
			<h1 className="text-2xl font-bold mb-4">TODO App</h1>

			{/* ローディング表示 */}
			{isLoading ? (
				<div className="text-center text-gray-500 my-4">Loading...</div>
			) : (
				<>
					{/* TODO追加フォーム */}
					<div className="mb-4">
						<input
							type="text"
							placeholder="新しいTODOを入力"
							className="w-full p-2 border rounded"
						/>
					</div>

					{/* TODOリスト */}
					<ul className="space-y-2">
						{todos.map((todo) => (
							<li
								key={todo.id}
								className="flex items-center justify-between p-3 bg-white border rounded shadow-sm"
							>
								<div className="flex items-center gap-2">
									<input
										type="checkbox"
										checked={todo.completed}
										onChange={() => {}} // 後で実装
										className="h-4 w-4"
									/>
									<span
										className={
											todo.completed ? "line-through text-gray-500" : ""
										}
									>
										{todo.title}
									</span>
								</div>
								<button
									type="button"
									className="text-red-500 hover:text-red-700"
								>
									削除
								</button>
							</li>
						))}
					</ul>
				</>
			)}
		</div>
	);
}

さいごに

バックエンドを実装したことで、Remixの理解がちょっとだけ深まりました!

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?