Remix で loader() でも action() でも同じデータを返すパターン
例えば従業員の一覧を表示する画面を Remix で書くとします。その画面が
普通にアクセスしたときは条件なしで従業員を表示する
同じ画面で検索条件を指定し従業員を絞り込むことができる
という動きをする場合、loader() も action() も両方使い、loader() では条件なしで取得したデータを、action() では指定した条件に合致するデータを返すことになりそうです。そういうときの実装について考えてみました。
Remix の「Form vs. fetcher」というページによると、このパターンの場合(同じ URL 内で画面から送信したデータを元に表示する内容を変化させる)は fetcher を使うのが良さそうです。
以下のような実装にしてみました。Form から to と from を送信、action() ではそれを使って employees のデータをフィルターしています。loader() では employees そのままを返しています。
import { ActionFunctionArgs, json } from "@remix-run/node";
import { useActionData, useFetcher, useLoaderData } from "@remix-run/react";
type Employee = {
name: string
age: number
}
const employees:Employee[] = [
{
name: "Alice",
age: 30,
},
{
name: "Bob",
age: 40,
},
{
name: "Charlie",
age: 50,
}
]
export function loader() {
return json(employees)
}
export async function action({request}:ActionFunctionArgs) {
const body = await request.formData()
const from = parseInt(String(body.get("from")), 10)
const to = parseInt(String(body.get("to")), 10)
return employees.filter(employee => (from <= employee.age && employee.age < to) )
}
export default function Index() {
const fetcher = useFetcher<typeof action>()
const employees = fetcher.data ?? useLoaderData<typeof loader>()
return (
<div>
<EmployeeList employees={employees} />
<fetcher.Form method="post">
<div>
<input type="number" name="from" placeholder="age from"/>
<input type="number" name="to" placeholder="age to (exclusive)"/>
<button type="submit" >Search</button>
</div>
</fetcher.Form>
</div>
)
}
function EmployeeList({ employees }:{ employees: Employee[] }) {
return (
<div>
{employees.map((employee, index) => <div key={index}>{employee.name}</div>)}
</div>
)
}
ポイントとしては Index() の冒頭で、fetcher.data からのデータの取得を試み、それができなかった場合は useLoaderData() からデータを取得している部分です。これにより、action() からデータが返されていたなら(つまり条件が指定された検索が行われた場合であれば)そのデータを、そうでない場合は loader() から返されたデータを利用する、という動きが実現されます。