【React Table V8 & TypeScript 】 ユーザー入力でセルを書き換える
こんにちはフロントエンドエンジニアの茶木です。
最近は学習用の Next.js の ローカルの SandBox を育てています。
今回は React Table による動的なデータの更新をどうするか書いていきます。
また、シリーズの途中で React Table を V7 → V8にアップデートしていますので、過去記事との差分を比較するときは参考にしてください
現状確認
過去記事でセルの装飾と加工を行っています。上記記事は React Table V7 で紹介しており、以下は テーブルコンポーネントの呼び出し箇所を V8に書き直したものです。
const columns = [
columnHelper.accessor("id", {
header: "id",
cell: IdCell,
}),
columnHelper.accessor("updatedTimestamp", {
header: "更新日",
cell: DateCell,
}),
columnHelper.accessor("name", {
header: "名前",
}),
columnHelper.accessor("age", {
header: "年齢",
}),
];
export default function V8() {
return (
<PageBase title={"V8"}>
<div>
<h1 className={styles.title}>V8</h1>
<Table<MatchResult> columns={columns} api={userApi} />
</div>
</PageBase>
);
}
ここでは、名前をユーザー入力によって変えられるようにします。
Cell 内の TextField
import { ReactElement } from "react";
import TextField from "@mui/material/TextField";
import { styled } from "@mui/material/styles";
import { BaseRow } from "../../../difinitions/table";
import { CellContext } from "@tanstack/react-table";
const Text = styled(TextField)({
fontWeight: "bold",
});
type Props<T extends BaseRow> = CellContext<T, string>;
export const NameCell = <T extends BaseRow>(props: Props<T>): ReactElement => {
return <Text defaultValue={props.getValue()} />;
};
テーブルセルにテキストフィールドを入れること自体は簡単です。ここでは MUI を使っています。
PutApiと連携する
変更された「名前」は TextField コンポーネントが保持しているだけなので、リロードすれば失われてしまいます。もちろん DB上の情報も更新されていません。
そこで、TextField の 変更を検知して putApi
をコールし DB を書き換えたあと、getApi を再度コールし、テーブルの更新を行えるようにします。
これには 2つの仕組みが必要です
- テーブルのリロードメソッドを作り
NameCell
まで届ける - TextField の変更を監視して、
putApi
の後、 リロードする
テーブルのリロード
useGetApi
ここから一苦労なのですが、 useGetApi
というフックを用意します。
import { AxiosResponse } from "axios";
import { useCallback, useEffect, useState } from "react";
export type ApiRes<Res> = Promise<AxiosResponse<Res>>;
export type UseGetApiRes<Res> = [Res | undefined, () => void];
export const useGetApi = <Res>(
getApi: () => ApiRes<Res>
): UseGetApiRes<Res> => {
const [data, setData] = useState<Res>();
const reload = useCallback(() => {
getApi().then((res) => {
setData(res.data);
});
console.log("reload");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
reload();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return [data, reload];
};
api
を渡すと tableData
と reload
を作ります。reload
を呼ぶと、api
がコールされ、tableData
が再度生成されます。
TableMeta
この reload
を useReactTable
の meta
に渡しておきます。 meta
は Cell
コンポーネント内でアクセスできます。 declare
を書いて置くことで vscode が meta.reload
にアクセスしたときに認識します。
declare module "@tanstack/table-core" {
interface TableMeta<TData extends RowData> {
reload: () => void;
}
}
const table = useReactTable<R>({
data: rows,
columns,
getCoreRowModel: getCoreRowModel(),
meta: {
reload,
},
});
データの更新
総決算です。TextField は制御コンポーネントに変えてあります。
ユーザーの入力により onChange から setValue がコールされ TextField の表示が変更されます。続いて onBlur
が呼ばれます。これを入力完了のトリガーとみなして、putApi
(DBの名前の値を更新する想定)をコールし、その後、テーブルリロードを行います。
テーブルのリロードが発生すると initialValue
の値が更新され useStateObserve
の働きで value
も更新され、TextField の値に最新の DBの値が表示されます。
export const NameCell = <T extends BaseRow>(props: Props<T>): ReactElement => {
const initialValue = props.getValue();
const [value, setValue] = useStateObserve(initialValue, [initialValue]);
const onBlur = useCallback(async() => {
await putApi(props.row.id, { name: value });
props.table.options.meta?.reload();
}, [value, props.row.id, props.table.options.meta]);
return (
<Text
value={value}
onBlur={onBlur}
onChange={(e) => setValue(e.target.value)}
/>
);
};
解説
meta.reload
リロードメソッドは、props.table.options.meta?.reload
として参照できます。
useReactTable
の meta
に指定したもの各セルで参照できます。ここでは更新関数 reload
を渡していました。
useStateObserve
useState(initialValue)
は 初回以降は initialValue
の変更を感知しません。感知するのは setState
によって value
を変更したときだけです。useStateObserve
は、setState
による value
の変更に加え、initialValue
の変更を感知し、value
を書き換えます。
これにより、ユーザー入力と、API による更新の両方で再描画が走ります。
useStateObserve
の実装内容については以下の記事をご参照ください。
Gaji-Laboでは、 Next.js 経験が豊富なフロントエンドエンジニアを募集しています
弊社では Next.js の知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違う Gaji-Labo を味わいに来ませんか?
Next.js の設計・実装を得意とするフロントエンドエンジニア募集要項
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!