[React] React Table で テーブル内のファジー(あいまい)検索 をしてみる | 心を無にして始める React
準備
TanStack Table v8 で React Table を使うのはこちら (*’▽’)
バックエンドには、いつもの json-server を使います。
検索
イメージ
シンプルな検索
検索用の入力フォームに文字を入力されると、検索を実行して結果を表示します。
検索方法には「入力された文字列が含まれているか」を採用します。
また、検索用の入力フォームに文字を入力して 0.5秒間 変化がなければ、検索を実行するようにします。
(入力中はすぐに検索せず、待機させます。)
今回は Table.js を編集していきます。
import React, { useEffect, useState } from 'react';
import { Table as BootstrapTable } from 'react-bootstrap';
import { useReactTable, flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel } from '@tanstack/react-table';
const filter = (row, columnId, value) => {
return String(row.getValue(columnId)).indexOf(value) !== -1;
}
const DebouncedInput = ({
value: initialValue,
onChange,
debounce = 500,
...props
}) => {
const [value, setValue] = useState(initialValue)
useEffect(() => {
setValue(initialValue)
}, [initialValue])
useEffect(() => {
const timeout = setTimeout(() => onChange(value), debounce);
return () => clearTimeout(timeout)
}, [value])
return (
<input {...props} value={value} onChange={e => setValue(e.target.value)} />
)
}
const Table = React.forwardRef(({
columns,
rows,
...otherProps
}, ref) => {
const [globalFilter, setGlobalFilter] = useState('')
const table = useReactTable({
columns,
data: rows,
state: {
globalFilter,
},
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: filter,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
})
return (
<div className="w-100">
<div className="mb-2 w-100" style={{ paddingLeft: 1, paddingRight: 1 }}>
<DebouncedInput
value={globalFilter ?? ''}
onChange={value => setGlobalFilter(String(value))}
className="w-100 p-2 font-lg shadow border border-block"
placeholder="検索"
/>
</div>
<BootstrapTable ref={ref} {...otherProps}>
<thead>
{
table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{
headerGroup.headers.map(header => {
return (
<th key={header.id}>
{
header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())
}
</th>
)
})
}
</tr>
))
}
</thead>
<tbody>
{
table.getRowModel().rows.map(row => (
<tr key={row.id}>
{
row.getVisibleCells().map(cell => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))
}
</tr>
))
}
</tbody>
</BootstrapTable>
</div>
)
})
export default Table;
App.js は 前の記事 と同じです。
少しだけカスタマイズ
さすがに、このままだと使えないので、ある程度使える形にします (*’▽’)
- スペース区切りは OR 条件として検索したい
- 大文字小文字は区別したくない
filter 関数を編集します (/・ω・)/
const filter = (row, columnId, value) => {
const values = value.split(/\s+/).map(x => x.toLowerCase());
const cellText = String(row.getValue(columnId)).toLowerCase();
return values.some(value => cellText.indexOf(value) !== -1);
}
スペース区切りを OR条件
ファジーな検索
検索方法を「あいまい検索」に変えます。
準備
追加でインストールするものがあります。
npm install @tanstack/match-sorter-utils
局所的なサンプル
filter 用の関数をファジー検索に対応した形にします。
import { rankItem } from '@tanstack/match-sorter-utils';
const fuzzyFilter = (row, columnId, value, addMeta) => {
const itemRank = rankItem(row.getValue(columnId), value);
addMeta({
itemRank,
})
return itemRank.passed;
}
ぜんたい
シンプルな検索の Table.js をベースに編集します。
import React, { useEffect, useState } from 'react';
import { Table as BootstrapTable } from 'react-bootstrap';
import { useReactTable, flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel } from '@tanstack/react-table';
import { rankItem } from '@tanstack/match-sorter-utils';
const fuzzyFilter = (row, columnId, value, addMeta) => {
const itemRank = rankItem(row.getValue(columnId), value);
addMeta({
itemRank,
})
return itemRank.passed;
}
const DebouncedInput = ({
value: initialValue,
onChange,
debounce = 500,
...props
}) => {
const [value, setValue] = useState(initialValue)
useEffect(() => {
setValue(initialValue)
}, [initialValue])
useEffect(() => {
const timeout = setTimeout(() => onChange(value), debounce);
return () => clearTimeout(timeout)
}, [value])
return (
<input {...props} value={value} onChange={e => setValue(e.target.value)} />
)
}
const Table = React.forwardRef(({
columns,
rows,
...otherProps
}, ref) => {
const [globalFilter, setGlobalFilter] = useState('')
const table = useReactTable({
columns,
data: rows,
state: {
globalFilter,
},
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: fuzzyFilter,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
debugTable: true,
})
return (
<div className="w-100">
<div className="mb-2 w-100" style={{ paddingLeft: 1, paddingRight: 1 }}>
<DebouncedInput
value={globalFilter ?? ''}
onChange={value => setGlobalFilter(String(value))}
className="w-100 p-2 font-lg shadow border border-block"
placeholder="検索"
/>
</div>
<BootstrapTable ref={ref} {...otherProps}>
<thead>
{
table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{
headerGroup.headers.map(header => {
return (
<th key={header.id}>
{
header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())
}
</th>
)
})
}
</tr>
))
}
</thead>
<tbody>
{
table.getRowModel().rows.map(row => (
<tr key={row.id}>
{
row.getVisibleCells().map(cell => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))
}
</tr>
))
}
</tbody>
</BootstrapTable>
</div>
)
})
export default Table;
はい、できました。