matsutoba’s blog

フロントエンドエンジニアをしています

Reactでのstateの変更とイベントの再登録について

windowにクリックイベントを登録して、クリックしたときにstateの状態によって処理を切り分けたいので、こう書いてみた。

import { useState, useEffect, useMemo } from "react";

export default function App() {
  const [id, setId] = useState("");

  const handler = () => {
    console.log("id", id);
    if (id === "1") {
      console.log("OK");
    } else {
      console.log("NG");
    }
  };

  useEffect(() => {
    window.addEventListener("click", handler);
    return () => {
      window.removeEventListener("click", handler);
    };
  }, []);

  console.log("State id", id);

  return (
    <div>
      <button
        onClick={() => {
          setId("0");
        }}
      >
        Set 0
      </button>
      <button
        onClick={() => {
          setId("1");
        }}
      >
        Set 1
      </button>
    </div>
  );
}

ボタンを押すとstateの状態が変わって再レンダリングされるのに、リスナに設定した関数の中は以前のstateの値を持ったままになってしまう。

イベントの登録をするuseEffectに依存配列としてstateの値を設定して、stateが変わったときにイベントを登録しなおせば、期待通りの動作になった。

  useEffect(() => {
    window.addEventListener("click", handler);
    return () => {
      window.removeEventListener("click", handler);
    };
  }, [id]);

useQueryでidにundefinedを指定したときにデフォルト値を返すキレイな書き方は?

指定したidに該当するデータを取得するとき、TanStack QueryのuseQueryを書くとしたら、

const fetchData = (id: number) => { ... }

const {data} = useQuery({
  queryFn: () => fetchData(id)
});

のようになるはず。

idがundefinedだったら?

idが number|undefined だとしたら、どう書くのがベストなのか?

// APIの引数をoptionalにするのはイケてない
const fetchData = (id?: number) => { ... }

const {data} = useQuery({
  queryFn: () => {
    // nullをreturnするのも変な感じ
    if (typeof id === 'undefined') return null;
    return fetchData(id)
  },
  enabled: typeof id !== 'undefined'
});

queryFnが返す値を変えれば良さそう?

github.com

こちらの議論を見たところ、これが良さそう。
これなら fetchDataの引数の型を不自然にしなくても良いし、Typescriptのlintも通る。

const fetchData = (id: number) => { ... }

const {data} = useQuery({
  queryFn: () => typeof id !== 'undefined' ? undefined : fetchData(id),
  enabled: typeof id !== 'undefined',
  initialData: { hoge: 123}  // デフォルト値を返すならinitialDataがあったほうが良いかも
});

react-base-tableに設定するデータはidが必要

テーブル形式のデータをReactで表示するときに、ソートや列の幅調整などもできるreact-base-tableライブラリを使うことにしました。

autodesk.github.io

サンプルを見ながら実装していたところ、セルに表示する文字列が長く3行以上になると何故かテーブルがチラつく(というか、レンダリングが無限ループしているような状態)になってしまいました。

rowKeyでデータを一意に特定するidの設定が必要

Get Startedのドキュメントを改めて確認したところ、unique Key の説明で、

unique key
key is required for column definition or the column will be ignored

Make sure each item in data is unique by a key, the default key is id, you can customize it via rowKey

と説明されており、デフォルト設定では "id" というプロパティをユニークキーにしていますよ、とハッキリ書いてありました。
確かにダミーデータにidは入れていなかった・・・。

なので、rowKeyは省略できるけれど、実際は

<BaseTable
  rowKey="id"
  :

ということなんですね。ユニークキーをidでは無いものにしたければ、ここの設定が変えれば良いと。


文字列の折返しをしてセルの高さを調整するとき、idが無いことで再レンダリングが止まらないのかなという予想で、改めてダミーデータにidを設定したら再レンダリングが無く、正しく動きました。



ドキュメントはしっかり読みましょう。