情報システム室の進地@日比谷です。
Remixでインクリメンタルサーチを実装する方法について記述します。
仕様
Homeページに設置した検索ボックスでインクリメンタルサーチを実装します。会社の情報が入ったDBがあるとして、会社名で候補検索する仕様を想定します。
ファイル構成
Remixの標準構成に app/model/company.ts
と app/routes/companies.tsx
を追加しています。
前者はCompanyデータ形と会社データのサンプルを記載し、後者には会社データを取得するloaderを記載しています。
app
├── entry.client.tsx
├── entry.server.tsx
├── models
│ └── company.ts
├── root.tsx
└── routes
├── _index.tsx
└── companies.tsx
コード
idとnameを文字列としてもつCompany
型を定義しています。また、getCompanies()
メソッドでダミーの会社情報を返すようにしています。
ここを外部DBなどから取得するように変更すれば当該DBに対してインクリメンタルサーチができるようになります。
app/models/company.ts
export type Company = {
id: string;
name: string;
};
export async function getCompanies(): Promise<Array<Company>> {
return [
{
id: "aaa",
name: "ウエキン合同会社",
},
{
id: "bbb",
name: "ウエキンコーポレーション",
},
{
id: "ccc",
name: "シンチ有限会社",
},
];
}
loaderを定義します。GETパラメータのterm
から検索文字列を受け取り、先述のgetCompanies()
で取得したデータに対してフィルタして返しています。
app/routes/companies.tsx
import { json, LoaderFunctionArgs } from "@remix-run/node";
import { Company, getCompanies } from "~/models/company";
export const loader = async ({
request,
}: LoaderFunctionArgs) => {
const url = new URL(request.url);
const term = url.searchParams.get('term');
return json(await searchCompany('' + term));
}
async function searchCompany(term: string): Promise<Array<Company>> {
const companies = await getCompanies();
return companies.filter(company => company.name?.match(term));
}
最後に検索窓を置くHome画面(Index)の実装です。
fetcherを使って、/companies?term=検索ワード
の呼び出しを行い、結果をfetcher.data
で受け取ってレンダリングします。
<input type="search">
と<datalist>
を組み合わせることで簡単に候補表示が可能です。
app/routes/_index.tsx
import type { MetaFunction } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
import { Company } from "~/models/company";
export const meta: MetaFunction = () => {
return [
{ title: "Home" },
{ name: "description", content: "This is your portal site!" },
];
};
export default function Index() {
const fetcher = useFetcher();
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<fetcher.Form>
<input
type="search"
onChange={(e) => {
const term = e.currentTarget.value;
fetcher.load(`/companies?term=${term}`);
}}
list="suggestions"
/>
<datalist id="suggestions">
{fetcher.data?.map((company: Company) => {
return (
<option key={company.id} value={company.name} />
)
})}
</datalist>
</fetcher.Form>
</div>
);
}