useEffect - Verhindert eine Endlosschleife beim Aktualisieren des Status

9

Ich möchte, dass der Benutzer eine Liste von Aufgabenelementen sortieren kann. Wenn der Benutzer ein Element aus einem Dropdown-Menü auswählt, wird festgelegt, sortKeywelches eine neue Version von erstellt setSortedTodos, und der useEffectAufruf wird wiederum ausgelöst setSortedTodos.

Das folgende Beispiel funktioniert genau so, wie ich es möchte, aber eslint fordert mich auf todos, das useEffectAbhängigkeitsarray zu erweitern, und wenn ich dies tue, verursacht es eine Endlosschleife (wie zu erwarten).

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]);

Live-Beispiel:

Ich denke, es muss einen besseren Weg geben, der eslint glücklich macht.

DanV
quelle
1
Nur eine Randnotiz: Der sortRückruf kann einfach sein: Dies return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());hat auch den Vorteil, dass ein Gebietsschemavergleich durchgeführt wird, wenn die Umgebung über angemessene Gebietsschemainformationen verfügt. Wenn Sie möchten, können Sie auch Destrukturierung darauf werfen: pastebin.com/7X4M1XTH
TJ Crowder
Welcher Fehler eslintwirft?
Luze
Könnten Sie die Frage aktualisieren, um mithilfe von Stack-Snippets ( Symbolleistenschaltfläche) ein lauffähiges, minimal reproduzierbares Beispiel für das Problem bereitzustellen [<>]? Stack-Snippets unterstützen React, einschließlich JSX. Hier erfahren Sie, wie Sie eine erstellen . Auf diese Weise können die Leute überprüfen, ob ihre vorgeschlagenen Lösungen nicht das Endlosschleifenproblem haben ...
TJ Crowder
Das ist ein wirklich interessanter Ansatz und ein wirklich interessantes Problem. Wie Sie sagen, können Sie verstehen, warum ESLint der Meinung ist, dass Sie todosdas Abhängigkeitsarray hinzufügen müssen useEffect, und Sie können sehen, warum Sie dies nicht tun sollten. :-)
TJ Crowder
Ich habe das Live-Beispiel für Sie hinzugefügt. Ich möchte wirklich, dass dies beantwortet wird.
TJ Crowder

Antworten:

8

Ich würde argumentieren, dass dies bedeutet, dass es nicht ideal ist, so vorzugehen. Die Funktion ist in der Tat abhängig von todos. Wenn setTodoses an einer anderen Stelle aufgerufen wird, muss die Rückruffunktion neu berechnet werden, andernfalls werden veraltete Daten verarbeitet.

Warum speichern Sie das sortierte Array überhaupt im Status? Sie können useMemodie Werte sortieren, wenn sich entweder der Schlüssel oder das Array ändert:

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]);

Dann sortedTodosüberall referenzieren .

Live-Beispiel:

Es ist nicht erforderlich, die sortierten Werte im Status zu speichern, da Sie das sortierte Array immer aus dem "Basis" -Array und dem Sortierschlüssel ableiten / berechnen können. Ich würde behaupten, es macht Ihren Code auch leichter verständlich, da er weniger komplex ist.

Felix Kling
quelle
Oh nette Verwendung von useMemo. Nur eine Nebenfrage, warum nicht .localComparein der Sorte verwenden?
Tikkes
2
Das wäre in der Tat besser. Ich habe gerade den OP-Code kopiert und das nicht beachtet (nicht wirklich relevant für das Problem).
Felix Kling
Wirklich einfache, leicht verständliche Lösung.
TJ Crowder
Ah ja, benutze Memo!
DanV
3

Der Grund für die Endlosschleife ist, dass die Aufgaben nicht mit der vorherigen Referenz übereinstimmen und der Effekt erneut ausgeführt wird.

Warum überhaupt einen Effekt für eine Klickaktion verwenden? Sie können es in einer Funktion wie der folgenden ausführen:

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);
}

und auf Ihrem Dropdown machen Sie eine onChange.

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

Hinweis zur Abhängigkeit übrigens, ESLint ist richtig! Ihre Todos sind im oben beschriebenen Fall eine Abhängigkeit und sollten in der Liste enthalten sein. Der Ansatz bei der Auswahl eines Artikels ist falsch und daher Ihr Problem.

Tikkes
quelle
2
"sort gibt eine neue Instanz eines Arrays zurück" Nicht die integrierte Sortiermethode. Es sortiert das Array an Ort und Stelle. data.slice(0)erstellt die Kopie.
Felix Kling
Dies ist nicht der Fall, wenn Sie a ausführen, setStateda das vorhandene Objekt nicht bearbeitet und daher intern geklont wird. Falsche Formulierung in meiner Antwort, stimmt. Ich werde das bearbeiten.
Tikkes
setStateklont keine Daten. Warum denkst du, dass?
Felix Kling
1
@Tikkes - Nein, klont setStatenichts. Felix und das OP sind korrekt. Sie müssen das Array kopieren, bevor Sie es sortieren können.
TJ Crowder
Okay ja, sorry. Ich muss anscheinend noch etwas über Interna lesen. Sie müssen in der Tat die vorhandenen kopieren und nicht ändern state.
Tikkes
0

Was Sie hier tun müssen, ist die funktionale Form von 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]);

Arbeitscodesandbox

Selbst wenn Sie den Status kopieren, um den ursprünglichen nicht zu mutieren, kann nicht garantiert werden, dass Sie den neuesten Wert erhalten, da der Status asynchron ist. Außerdem geben die meisten Methoden eine flache Kopie zurück, sodass Sie möglicherweise trotzdem den ursprünglichen Zustand ändern.

Durch die Verwendung von function wird setStatesichergestellt, dass Sie den neuesten Wert des Status erhalten und den ursprünglichen Statuswert nicht mutieren.

Klarheit
quelle
"und mutieren Sie nicht den ursprünglichen Statuswert." Ich glaube nicht, dass React eine Kopie an den Rückruf weitergibt. .sortÄndert das Array an Ort und Stelle, sodass Sie es immer noch selbst kopieren müssen.
Felix Kling
Hmm, guter Punkt, ich kann tatsächlich keine Bestätigung finden, dass es die Kopie des Staates besteht, obwohl ich mich daran erinnere, dass ich so etwas früher gelesen habe.
Klarheit
Fand es hier: reactjs.org/docs/… . 'Wir können das funktionale Update-Formular von setState verwenden. Hier können wir angeben, wie sich der Status ändern muss, ohne auf den aktuellen Status zu verweisen. '
Klarheit
Ich lese das nicht als Kopie. Es bedeutet nur, dass Sie den aktuellen Status nicht als "externe" Abhängigkeit angeben müssen.
Felix Kling