Next.js で TanStack Table v8 を使用する場合の描画ループに対処してみた

2023.02.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。

Next.jsTanStack Table v8を使用した際に、以下のような描画ループに関するメッセージが発生して困っていました。

今回は、この事象への対応について書きたいと思います。

そもそもどんなエラーなのか

エラーメッセージを日本語にすると、以下のようなメッセージとなります。

一方で、今回この事象が発生したコードでは useEffect は利用していないので、似通った問題に当たりを付けて原因を探ってみました。

事象が発生したコードについて

今回、事象が発生したコードは、以下のサンプルを元に作成したコードでした。

1つ違うポイントとしては、テーブルに利用するデータはサンプルのダミーデータではなく実データとなるので、SWRを利用して取得しています。

そのため、該当コードは以下のようなイメージになります。

function Profile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher)

  // TanStack Table 用の設定、準備をする
  xxxxx

  if (error) return <div>failed to load</div>
  if (isLoading) return <div>loading...</div>
  return (
    <div>
      <!-- TanStack Tableを利用してテーブルを描画する -->
      xxxxx
    </div>
  )
}

isLoadingtrue の場合はローディング表示をして、ロードが完了したらテーブルを描画したいです。

エラーの発生原因を特定する

エラーのスタックトレースとコードをにらめっこしたところ、以下が原因となって描画ループが発生していました。

また、サンプルコードにはありませんが table.getSelectedRowModel().rows を利用している箇所でも発生していました。

エラーに対応してみた

table.getRowModel().rows と table.getSelectedRowModel().rows

描画ループが発生している = メモ化をすべき と考え、以下のようにuseMemoを利用してメモ化を行いました。

  const selectedRows: Row<XXXXX>[] = useMemo(() => {
    return table.getSelectedRowModel().rows
  }, [table, table.getIsSomeRowsSelected(), table.getIsAllRowsSelected()])

  const rows: Row<XXXXX>[] = useMemo(() => {
    return table.getRowModel().rows
  }, [table, isLoading, globalFilter])

Row[]

XXXXXの型には各rowで利用しているデータ型を適宜しています。useReactTableで、dataとして指定するデータの型ですね。

selectedRows

ここでは依存関係として、tableの他に明示的にtable.getIsSomeRowsSelected()table.getIsAllRowsSelected()を指定しています。

これにより、テーブルでチェックボックスの選択状態が変わった時に、選択中の行であるtable.getSelectedRowModel().rowsを更新するようにしました。

rows

ここでは依存関係として、tableの他に明示的にisLoadingglobalFilterを指定しています。

これにより、データロードが完了した時と、フィルタ条件が変わったときに、テーブルの表示行であるtable.getRowModel().rowsを更新するようにしました。

table.getIsAllRowsSelected()

ここについては、上手い解決方法が見つかっていません。

実際の利用箇所は以下のようなイメージとなります。

  const columns = React.useMemo<ColumnDef<Person>[]>(
    () => [
      {
        id: 'select',
        header: ({ table }) => (
          <IndeterminateCheckbox
            {...{
              checked: table.getIsAllRowsSelected(),
    (...略...)
    ],
    []
  )

  const table = useReactTable({
    data,
    columns,
    (...略...)
  })

ここで、table.getIsAllRowsSelected()をメモ化しようとすると、必然的にcolumnsのコードの前でconst定義をすることになります。

一方で、その時点ではtableの定義がされていないので、依存関係の定義がうまくできずに「鶏が先か、卵が先か」のようなジレンマが発生してしまいます。

もし良い方法を思いついたら、また追記をしたいと思います。

まとめ

以上、Next.js で TanStack Table v8 を使用する場合の描画ループに対処してみました。

Next.js を利用していると割と良く描画ループ問題に直面するので、今回のメモ化での対応も含めて対応方法を忘れないようにしたいと思います。

どなたかのお役に立てば幸いです。それでは!