在 todos 中可以當作任務是否完成的選取框框,也可以作為列的選取手段,這樣的設計好像很少見,我之前其實沒有用過 react-table 來做列選擇功能,所以這對我來說也是第一次,十分新鮮。這個功能是用選取框框勾選的方式來選到表格的一列,能夠一列一列選,也能夠全部一起選。
繼續完成 TodoPge 中表格選取的功能吧,先從 react-table 引入必要的工具 :
import {
useReactTable, // 掌管整個 table 的重要 hook
getCoreRowModel, // 表格 row 的核心模型
flexRender, // 用來渲染 flex box
} from "@tanstack/react-table";
根據前一天提到的事情,我們會需要給 table 輸入 data 跟 columns,先來定義好 :
const [data, setData] = useState(() => [...list]);
const columns = useMemo(
() => [
{
id: "select",
header: ({ table }) => (
<IndeterminateCheckbox
className="form-input border-[2px] rounded border-gray-300 w-[20px] h-[20px]"
{...{
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler(),
}}
/>
),
cell: ({ row }) => (
<div className="px-1">
<IndeterminateCheckbox
className="form-input border-[2px] rounded border-gray-300 w-[20px] h-[20px]"
{...{
checked: row.getIsSelected(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler(),
}}
/>
</div>
),
},
{ header: "任務標題", accessorKey: "title" },
{ header: "詳情", accessorKey: "info" },
],
[]
);
原本我使用 chakra UI 的 CheckBox,不過我在這裡改用官方網站提供的範例來做,變回使用原生的 input :
function IndeterminateCheckbox({
indeterminate,
className = "",
...rest
}: { indeterminate?: boolean } & HTMLProps<HTMLInputElement>) {
const ref = useRef<HTMLInputElement>(null!);
useEffect(() => {
if (typeof indeterminate === "boolean") {
ref.current.indeterminate = !rest.checked && indeterminate;
}
}, [ref, indeterminate]);
return (
<input
type="checkbox"
ref={ref}
className={className + " cursor-pointer"}
{...rest}
/>
);
}
不過我有自己把樣式重寫成自己喜歡的樣子:P 嘿嘿 :
<IndeterminateCheckbox
className="form-input border-[2px] rounded border-gray-300 w-[20px] h-[20px]" // 這裡
{...{
checked: row.getIsSelected(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler(),
}}
/>
</div>
之後把它們丟進 useReactTable() 中初始化 :
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
接下來就可以嘗試改用 react-table 的方式輸出表格 :
...
<Thead>
{table.getHeaderGroups().map((headerGroup) => (
<Tr>
{headerGroup.headers.map((header) => (
<Th>
{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>
...
要給 table 選取列的功能,要先幫他做一個 state,並且放進去 table 裡面 :
import {
const [rowSelection, setRowSelection] = useState({});
const table = useReactTable({
data,
columns,
state: {
rowSelection, // 這裡
},
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(), // 這裡
getSortedRowModel: getSortedRowModel(),
});
如果沒有接下來的步驟,會發現 react-table 並不會因為 list 的新增而更新表格內容,不過這個解決辦法並不困難。
首先,使用監聽 list,並且在 list 改變之時將它 set 回 data,當 data 改變時,表格才會更新 :
useEffect(() => setData([...list]), [list]);
使用 react-table 最難的不是安裝功能上去,而是前面的定義與把表格如自己想要的樣子輸出在畫面上...基本上只要能夠做到以上兩件事情,其他的功能都可以讓 react-table 協助完成。
學習的難度是有,但之後功能能夠成功運轉起來,成就感也非凡。