やりたかったこと
DataGridで作成した表のセルの中にボタンを配置し、ボタンを押すと押した行の特定の値がuseStateで設定したStateにどんどん追加されていくような仕組みを作りたかった。
App.tsx
function App() {
// ボタンが押された行のValueを追加していく配列のState
const [data, setData] = useState<string>([]);
// ボタンが押された時の処理
const handleClick = (value: string) => {
setData([...data, value]);
};
// カラムの定義。ボタンを配置するセルにはrenderCellを利用。
const column: GridColDef[] = [
{ field: "id", headerName: "ID" },
{ field: "value", headerName: "Value" },
{
field: "button",
headerName: "Button",
renderCell: (params) => <Button onClick={() => handleClick(params.row.value)} ><AddBoxIcon /></Button>
}
];
{/* 色々と省略 */}
return (
<DataGrid
columns={column}
rows={row}
/>
);
}
起きたこと
ボタンを押すたびにStateの配列に値が追加されていくつもりだったが、実際は最後に押した行の値しか入っていないという悲しい状態が発生した。
対策
renderCellでレンダリングされたボタンの関数が初期値のまま更新されていないことが原因っぽい。
ありがたい助言をもらって回避できた方法は以下の通り。
App.tsx
function App () {
const [data, setData] = useState<string>([]);
const handleClick = (value: string) => {
setData([...data, value]);
};
// 使用する関数の型が入るようにuseRefを設定
const handleClickRef = useRef<(value: number) => void>();
// dataのStateが変更されるたびに関数のuseRefに最新の関数を登録
useEffect(() => {
handleClickRef.current = handleClick;
},[data]);
const column: GridColDef[] = [
{ field: "id", headerName: "ID" },
{ field: "value", headerName: "Value" },
{
field: "button",
headerName: "Button",
// 呼び出し時にuseRefがundefinedの場合を考慮しないと怒られるので関数が登録されていない時はnullを返すようにする
renderCell: (params) => <Button onClick={() => handleClickRef.current ? handleClickRef.current(params.row.value) : null} ><AddBoxIcon /></Button>
}
];
// ...以下省略
}