useEffect-状態を更新するときに無限ループを防ぐ


9

ユーザーがToDoアイテムのリストを並べ替えられるようにしたい。ユーザーがドロップダウンリストから項目を選択すると、それが設定されますsortKeyの新しいバージョンを作成するであろうsetSortedTodos、とターントリガーでuseEffect、コールをsetSortedTodos

以下の例は、私が望んでいるとおりに機能しますが、eslintは依存関係配列に追加todosするように求めています。そうするとuseEffect、無限ループが発生します(予想どおり)。

const [todos, setTodos] = useState([]);
const [sortKey, setSortKey] = useState('title');

const setSortedTodos = useCallback((data) => {
  const cloned = data.slice(0);

  const sorted = cloned.sort((a, b) => {
    const v1 = a[sortKey].toLowerCase();
    const v2 = b[sortKey].toLowerCase();

    if (v1 < v2) {
      return -1;
    }

    if (v1 > v2) {
      return 1;
    }

    return 0;
  });

  setTodos(sorted);
}, [sortKey]);

useEffect(() => {
    setSortedTodos(todos);
}, [setSortedTodos]);

実例:

これを行うには、eslintを幸せに保つより良い方法が必要だと思います。


1
余談ですが、sortコールバックは次のようにすることができます。return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());これには、環境に適切なロケール情報がある場合にロケール比較を実行できるという利点もあります。必要に応じ
TJ Crowder

どのようなエラーが発生していますeslintか?
ルツェ

質問を更新して、スタックスニペット(ツールバーボタン)を使用して、問題の実行可能な最小限の再現可能な例を提供できます[<>]か?スタックスニペットは、JSXを含むReactをサポートしています。これを行う方法は次のとおりです。そうすれば、提案されたソリューションに無限ループの問題がないことを人々が確認できます...
TJ Crowder

これは非常に興味深いアプローチであり、非常に興味深い問題です。あなたが言うように、ESLintがtodosの依存関係配列に追加する必要があると考える理由を理解でき、そうしない理由を理解useEffectできます。:-)
TJクラウダー

ライブサンプルを追加しました。本当にこの回答を見たいです。
TJクラウダー

回答:


8

これは、これをこの方法で行うことは理想的ではないことを意味すると主張します。この関数は確かにに依存していtodosます。setTodosどこかで呼び出された場合、コールバック関数を再計算する必要があります。それ以外の場合は、古いデータを操作します。

なぜソートされた配列をとにかく状態で保存するのですか?を使用useMemoして、キーまたは配列のいずれかが変更されたときに値をソートできます。

const sortedTodos = useMemo(() => {
  return Array.from(todos).sort((a, b) => {
    const v1 = a[sortKey].toLowerCase();
    const v2 = b[sortKey].toLowerCase();

    if (v1 < v2) {
      return -1;
    }

    if (v1 > v2) {
      return 1;
    }

    return 0;
  });
}, [sortKey, todos]);

次に、sortedTodosどこでも参照してください。

実例:

「ベース」配列とソートキーから常にソートされた配列を導出/計算できるため、ソートされた値を状態に格納する必要はありません。また、コードの複雑さが軽減されるため、コードを理解しやすくなります。


ああのいい使い方useMemo.localCompare余談ですが、並べ替えで使用してみませんか?
-Tikkes、

2
それは確かに良いでしょう。私はOPのコードをコピーしただけで、それには注意を払いませんでした(実際には問題には関係ありません)。
Felix Kling、

本当にシンプルで理解しやすいソリューション。
TJクラウダー

ああ、useMemo!そのことを忘れてしまった:)
DanV

3

無限ループの理由は、todosが前の参照と一致せず、効果が再実行されるためです。

とにかくクリックアクションにエフェクトを使用するのはなぜですか。あなたはそのような関数でそれを実行することができます:

const [todos, setTodos] = useState([]);

function sortTodos(e) {
    const sortKey = e.target.value;
    const clonedTodos = [...todos];
    const sorted = clonedTodos.sort((a, b) => {
        return a[sortKey.toLowerCase()].localeCompare(b[sortKey.toLowerCase()]);
    });

    setTodos(sorted);
}

ドロップダウンでを実行しますonChange

    <select onChange="sortTodos"> ......

ちなみに依存関係に注意してください、ESLintは正しいです!上記のケースでは、Todoは依存関係であり、リストに含まれている必要があります。アイテムの選択に関するアプローチが間違っているため、問題が発生しています。


2
「sortは配列の新しいインスタンスを返します」組み込みのsortメソッドではありません。配列をインプレースで並べ替えます。data.slice(0)コピーを作成します。
Felix Kling

これはsetState既存のオブジェクトを編集せず、そのため内部的に複製するため、aを実行する場合には当てはまりません。私の答えの誤った表現、本当です。これを編集します。
Tikkes

setStateデータを複製しません。なぜそう思うの?
Felix Kling

1
@Tikkes-いいえ、setState何もクローンしません。FelixとOPは正しいので、並べ替える前に配列をコピーする必要があります。
TJクラウダー

わかりました、すみません。どうやら内部についてもう少し読む必要がある。実際に、既存のものをコピーして変更しないでくださいstate
Tikkes

0

ここで行う必要があるのは、setState次の関数形式を使用することです。

  const [todos, setTodos] = useState(exampleToDos);
    const [sortKey, setSortKey] = useState('title');

    const setSortedTodos = useCallback((data) => {

      setTodos(currTodos => {
        return currTodos.sort((a, b) => {
          const v1 = a[sortKey].toLowerCase();
          const v2 = b[sortKey].toLowerCase();

          if (v1 < v2) {
            return -1;
          }

          if (v1 > v2) {
            return 1;
          }

          return 0;
        });
      })

    }, [sortKey]);

    useEffect(() => {
        setSortedTodos(todos);
    }, [setSortedTodos, todos]);

作業コードサンドボックス

元の状態を変更しないように状態をコピーしている場合でも、状態を非同期に設定しているため、最新の値を取得できるとは限りません。さらに、ほとんどのメソッドは浅いコピーを返すため、とにかく元の状態を変更してしまう可能性があります。

機能setStateを使用すると、状態の最新の値を取得し、元の状態の値を変更しないことが保証されます。


「元の状態の値を変更しないでください。」Reactがコピーをコールバックに渡したとは思いません。.sort配列をインプレースで変更するので、自分でコピーする必要があります。
Felix Kling、

うーん、良い点、私は実際にそれが状態のコピーを通過することの確認を見つけることができません。
クラリティ

ここで見つかりました:reactjs.org/docs/…。'setStateの機能更新フォームを使用できます。これにより、現在の状態を参照せずに状態を変更する方法を指定できます'
Clarity

コピーを手に入れているとは思わない。これは、現在の状態を「外部」依存関係として提供する必要がないことを意味します。
Felix Kling
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.