useEffect - Zapobiega nieskończonej pętli podczas aktualizacji stanu


9

Chciałbym, aby użytkownik mógł posortować listę rzeczy do zrobienia. Gdy użytkownicy wybiorą element z listy rozwijanej, ustawi, sortKeyktóra utworzy nową wersję setSortedTodos, a następnie uruchomi useEffecti zadzwonisetSortedTodos .

Poniższy przykład działa dokładnie tak, jak chcę, jednak eslint zachęca mnie do dodania todosdo useEffecttablicy zależności, a jeśli to zrobię, spowoduje nieskończoną pętlę (jak można się spodziewać).

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

Przykład na żywo:

Myślę, że musi istnieć lepszy sposób na zrobienie tego, co zapewni zadowolenie eslintowi.


1
Uwaga dodatkowa: sortwywołanie zwrotne może być po prostu: return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());co ma tę zaletę, że porównuje ustawienia narodowe, jeśli środowisko ma rozsądne informacje o ustawieniach regionalnych. Jeśli chcesz, możesz też rzucić w to destrukcją: pastebin.com/7X4M1XTH
— TJ Crowder

Jaki błąd jest eslintgenerowany?
— Luze,

Czy możesz zaktualizować pytanie, aby uzyskać działający minimalny odtwarzalny przykład problemu przy użyciu fragmentów stosu ( [<>]przycisk paska narzędzi)? Fragmenty stosu obsługują React, w tym JSX; oto jak to zrobić . W ten sposób ludzie mogą sprawdzić, czy ich proponowane rozwiązania nie mają problemu z nieskończoną pętlą ...
— TJ Crowder

To naprawdę ciekawe podejście i naprawdę interesujący problem. Jak mówisz, możesz zrozumieć, dlaczego ESLint uważa, że ​​musisz dodać todosdo tablicy zależności useEffect, i możesz zobaczyć, dlaczego nie powinieneś. :-)
— TJ Crowder

Dodałem dla ciebie przykład na żywo. Naprawdę chcę zobaczyć odpowiedź.
— TJ Crowder

Odpowiedzi:


8

Twierdzę, że oznacza to, że takie podejście nie jest idealne. Funkcja jest rzeczywiście zależna od todos. GdybysetTodos zostanie wywołany w innym miejscu, funkcja wywołania zwrotnego musi zostać ponownie obliczona, w przeciwnym razie działa na nieaktualnych danych.

Dlaczego mimo wszystko przechowujesz posortowaną tablicę w stanie? Możesz użyć useMemodo sortowania wartości, gdy zmienia się klucz lub tablica:

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

Następnie odniesienie sortedTodos wszędzie.

Przykład na żywo:

Nie ma potrzeby przechowywania posortowanych wartości w stanie, ponieważ zawsze można wyprowadzić / obliczyć posortowaną tablicę z tablicy „podstawowej” i klucza sortowania. Twierdzę, że ułatwia to również zrozumienie kodu, ponieważ jest mniej skomplikowany.


Och, niezłe użycie useMemo. Tylko pytanie poboczne, dlaczego nie użyć .localComparetego rodzaju?
— Tikkes,

2
To byłoby rzeczywiście lepsze. Właśnie skopiowałem kod OP i nie zwróciłem na to uwagi (niezbyt istotne dla problemu).
— Felix Kling

Naprawdę proste, łatwe do zrozumienia rozwiązanie.
— TJ Crowder,

Ach tak, użyjMemo! Zapomniałem o tym :)
— DanV

3

Powodem nieskończonej pętli jest to, że todos nie pasują do poprzedniego odwołania i efekt zostanie uruchomiony ponownie.

Po co w ogóle korzystać z efektu kliknięcia? Możesz uruchomić go w takiej funkcji:

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

a po rozwijaniu wykonaj onChange.

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

Przy okazji, zwróć uwagę na zależność, ESLint ma rację! Twoje zadania do wykonania, w przypadku opisanym powyżej, są zależne i powinny znajdować się na liście. Podejście do wyboru przedmiotu jest niewłaściwe, a zatem twój problem.


2
„sort zwróci nową instancję tablicy”, a nie wbudowana metoda sortowania. Sortuje tablicę w miejscu. data.slice(0)tworzy kopię.
— Felix Kling

Nie jest to prawdą, kiedy to zrobisz, setStateponieważ nie będzie edytować istniejącego obiektu, a zatem sklonuje go wewnętrznie. Nieprawidłowe sformułowanie w mojej odpowiedzi, prawda. Zmienię to.
— Tikkes,

setStatenie klonuje danych. Dlaczego myślisz, że?
— Felix Kling

1
@Tikkes - Nie, setStateniczego nie klonuje . Felix i OP mają rację, musisz skopiować tablicę przed posortowaniem.
— TJ Crowder

Okej, przepraszam. Wydaje mi się, że potrzebuję trochę więcej informacji na temat elementów wewnętrznych. Rzeczywiście musisz skopiować i nie zmieniać istniejącego state.
— Tikkes,

0

Musisz tutaj użyć funkcjonalnej formy 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]);

Działające kody i skrzynka

Nawet jeśli kopiujesz stan, aby nie mutować oryginalnego, nadal nie ma gwarancji, że uzyskasz jego najnowszą wartość, ponieważ ustawienie stanu jest asynchroniczne. Ponadto większość metod zwróci płytką kopię, więc i tak może dojść do mutacji stanu oryginalnego.

Korzystanie z funkcji setStatezapewnia, że ​​otrzymujesz najnowszą wartość stanu i nie mutujesz oryginalnej wartości stanu.


„i nie mutuj oryginalnej wartości stanu”. Nie sądzę, aby React przekazał kopię do wywołania zwrotnego. .sortzmienia tablicę w miejscu, więc nadal musisz ją skopiować samodzielnie.
— Felix Kling

Hmm, dobra uwaga, właściwie nie mogę znaleźć żadnego potwierdzenia, że ​​przekazuje kopię stanu, choć pamiętam, że czytałem coś takiego wcześniej.
— Clarity

Znalazłem to tutaj: reagjs.org/docs/… . „Możemy użyć funkcjonalnej formy aktualizacji setState. Pozwala nam określić, w jaki sposób stan powinien się zmienić bez odwoływania się do bieżącego stanu ”
— Clarity,

Nie czytam tego jako otrzymania kopii. Oznacza to po prostu, że nie musisz podawać bieżącego stanu jako zależności „zewnętrznej”.
— Felix Kling
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.