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

## はじめに
[前回](https://qiita.com/koji0705/items/811104b6e32194ed03cc)の続きとなります。
今回はバックエンド側の実装をし、フロントとの繋ぎ込みを実施します！

## シリーズもの
- [Step 1: 基本的なセットアップとTailwindCSS導入](https://qiita.com/koji0705/items/811104b6e32194ed03cc)
  Remixプロジェクトの作成、TailwindCSSのセットアップ、静的なTODOリストの表示

- [Step 2: バックエンドとの疎通](https://qiita.com/koji0705/items/cfc1ae92e8aefd1b3a24)
  Remixのloader関数を使ったデータフェッチ、型定義の作成、ローディング状態の実装

- [Step 3: アクションの実装](https://qiita.com/koji0705/items/ae278fb238a2e0aca205)
  TODOの追加・更新・削除機能の実装、コンポーネントの分割とリファクタリング

- [Step 4: データベース連携](https://qiita.com/koji0705/items/927d91871bb145230117)
  PrismaとSQLiteを使用したデータの永続化、マイグレーションの実行、CRUD操作の実装


## 成果物
![scsho2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/8befa939-9761-403e-8546-48fd74391400.gif)
なんのて変哲もない表示です。ただ、前回まではフロントの固定値を返却していましたが、今回は**loader**を使ってバックエンドの値を元にバインドしています。

https://github.com/kouji0705/remix_sample/pull/2

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

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

### シンプルな例
```typescript
// サーバーサイド: データを準備
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が便利か？
- データ取得のロジックをサーバーに集中できる
- コンポーネントはデータの表示に集中できる
- 型安全にデータをやり取りできる


## 今回やりたいこと


```mermaid
sequenceDiagram
    actor User
    participant Browser
    participant Remix_Client
    participant Remix_Server
    participant Mock_API

    User->>Browser: ページにアクセス
    Browser->>Remix_Server: GETリクエスト
    
    Note over Remix_Server: loader関数実行
    Remix_Server->>Mock_API: モックデータ取得
    Note over Mock_API: 500ms待機
    Mock_API-->>Remix_Server: モックデータ返却
    
    Remix_Server-->>Browser: HTML + JSONデータ

    Note over Browser,Remix_Client: Hydration開始
    Browser->>Remix_Client: 制御を移管
    Remix_Client->>Remix_Client: useLoaderDataでデータ取得
    
    Note over Remix_Client: Reactコンポーネントの<br/>インタラクティブ化
    
    Remix_Client->>Browser: インタラクティブなTODOリスト表示
    Browser->>User: 完全に操作可能なUI
```

## ソースコード
```shell:ディレクトリ構成
~/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
```


```ts: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;
}

```


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

```

```ts: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の理解がちょっとだけ深まりました！