[React] React Table で ソート をしてみる | 心を無にして始める React
今回は、前回の ファジー検索 の機能を付けたものに ソート の機能を加えていきます。
準備
TanStack Table v8 で React Table を使うのはこちら (*’▽’)
バックエンドには、いつもの json-server を使います。
ソート
イメージ
局所的なサンプル
ソートできる場合にはマウスカーソルを 指のマーク に変更して、ソートの状態を ▲▼ で表現しています。
TanStack Table はカスタマイズの自由度が高い分、なんとなく使い方は難し目な気がしてきました 💦
{
header.isPlaceholder
? null
: (
<div
{...{
style: {
cursor: header.column.getCanSort()
? 'pointer'
: '',
},
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{
{
asc: <span className="ps-3" style={{ fontSize: '50%' }}>▲<span style={{ color: '#333' }}>▼</span></span>,
desc: <span className="ps-3" style={{ fontSize: '50%' }}><span style={{ color: '#333' }}>▲</span>▼</span>,
}[header.column.getIsSorted()]
?? <span className="ps-3" style={{ fontSize: '50%', color: '#333' }}>▲▼</span>
}
</div>
)
}
サンプル
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())
} */}
{
header.isPlaceholder
? null
: (
<div
{...{
style: {
cursor: header.column.getCanSort()
? 'pointer'
: '',
},
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{
{
asc: <span className="ps-3" style={{ fontSize: '50%' }}>▲<span style={{ color: '#333' }}>▼</span></span>,
desc: <span className="ps-3" style={{ fontSize: '50%' }}><span style={{ color: '#333' }}>▲</span>▼</span>,
}[header.column.getIsSorted()]
?? <span className="ps-3" style={{ fontSize: '50%', color: '#333' }}>▲▼</span>
}
</div>
)
}
</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 は特に変更ありませんが、ぺたり。
import axios from 'axios';
import { Suspense, useState } from 'react';
import { Spinner } from 'react-bootstrap';
import './App.css';
import Table from './components/Table';
axios.defaults.headers.get['Content-Type'] = 'application/json';
axios.defaults.headers.get.Accept = 'application/json';
axios.defaults.baseURL = 'http://localhost:3000/';
const COLUMNS = [
{
accessorKey: 'id',
header: 'ID',
},
{
accessorKey: 'name',
header: 'なまえ',
},
];
const Pets = ({
values,
setState,
}) => {
if (values === null) {
if (setState) {
throw axios.get('/cats').then(response => setState(response.data));
}
}
return (
<Table
bordered
hover
striped
variant="dark"
columns={COLUMNS}
rows={values}
/>
);
};
function App() {
const [cats, setCats] = useState(null);
return (
<div className="App">
<header className="App-header p-5">
<Suspense fallback={<Spinner animation="border" variant="light" />}>
<Pets values={cats} setState={setCats} />
</Suspense>
</header>
</div>
);
}
export default App;
はい、できました。