useEffect - Mencegah loop tak terbatas saat memperbarui status


9

Saya ingin pengguna dapat mengurutkan daftar item todo. Ketika pengguna memilih item dari dropdown itu akan mengatur sortKeyyang akan membuat versi baru setSortedTodos, dan pada gilirannya memicu useEffectdan memanggil setSortedTodos.

Contoh di bawah ini berfungsi persis seperti yang saya inginkan, namun eslint mendorong saya untuk menambahkan todoske useEffectarray dependancy, dan jika saya lakukan itu menyebabkan loop tak terbatas (seperti yang Anda harapkan).

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

Contoh Langsung:

Saya pikir harus ada cara yang lebih baik untuk melakukan ini yang membuat eslint bahagia.


1
Hanya catatan tambahan: sortCallback bisa adil: return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());yang juga memiliki keunggulan melakukan perbandingan lokal jika lingkungan memiliki informasi lokal yang masuk akal. Jika Anda suka, Anda juga dapat melakukan penghancuran padanya: pastebin.com/7X4M1XTH
TJ Crowder

Apa kesalahan eslintmelempar?
Luze

Bisakah Anda memperbarui pertanyaan untuk memberikan contoh minimal yang dapat direproduksi dari masalah menggunakan Stack Snippets ( [<>]tombol bilah alat)? Stack Snippets mendukung React, termasuk JSX; inilah cara melakukannya . Dengan begitu orang dapat memeriksa bahwa solusi yang mereka tawarkan tidak memiliki masalah loop tak terbatas ...
TJ Crowder

Itu pendekatan yang sangat menarik, dan masalah yang sangat menarik. Seperti yang Anda katakan, Anda dapat memahami mengapa ESLint berpikir Anda perlu menambahkan todoske array dependensi useEffect, dan Anda dapat melihat mengapa Anda tidak perlu. :-)
TJ Crowder

Saya menambahkan contoh langsung untuk Anda. Benar-benar ingin melihat ini dijawab.
TJ Crowder

Jawaban:


8

Saya berpendapat bahwa ini berarti bahwa melakukannya dengan cara ini tidak ideal. Fungsi ini memang tergantung todos. Jika setTodosdipanggil di tempat lain, fungsi callback harus dihitung ulang, jika tidak beroperasi pada data basi.

Mengapa Anda menyimpan array yang disortir dalam keadaan? Anda bisa menggunakan useMemountuk mengurutkan nilai saat kunci atau array berubah:

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

Lalu referensi sortedTodoskemana-mana.

Contoh Langsung:

Tidak perlu menyimpan nilai yang diurutkan dalam keadaan, karena Anda selalu dapat menurunkan / menghitung array yang diurutkan dari array "base" dan kunci sortir. Saya berpendapat itu juga membuat kode Anda lebih mudah dipahami karena kurang rumit.


Oh, bagus digunakan useMemo. Hanya pertanyaan sampingan, mengapa tidak digunakan .localComparedalam pengurutan?
Tikkes

2
Itu memang akan lebih baik. Saya baru saja menyalin kode OP dan tidak memperhatikan itu (tidak benar-benar relevan dengan masalah).
Felix Kling

Sangat sederhana, solusi mudah dimengerti.
TJ Crowder

Ah ya, gunakanMemo! Lupa tentang itu :)
DanV

3

Alasan untuk infinite loop adalah karena todos tidak cocok dengan referensi sebelumnya dan efeknya akan dijalankan kembali.

Mengapa tetap menggunakan efek untuk tindakan klik? Anda dapat menjalankannya dalam fungsi seperti ini:

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

dan pada dropdown Anda lakukan onChange.

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

Perhatikan ketergantungannya, ESLint benar! Todos Anda, dalam kasus yang dijelaskan di atas, adalah ketergantungan dan harus ada dalam daftar. Pendekatan pada pemilihan item salah, dan karenanya masalah Anda.


2
"sort akan mengembalikan instance array baru" Bukan metode sortir bawaan. Ini mengurutkan array di tempat. data.slice(0)membuat salinan.
Felix Kling

Itu tidak benar ketika Anda melakukan setStatekarena itu tidak akan mengedit objek yang ada dan karenanya akan mengkloningnya secara internal. Kata-kata yang salah dalam jawaban saya, benar. Saya akan mengedit ini.
Tikkes

setStatetidak mengkloning data. Mengapa kamu berpikir begitu?
Felix Kling

1
@Tikkes - Tidak, setStatetidak mengkloning apa pun. Felix dan OP sudah benar, Anda perlu menyalin array sebelum mengurutkannya.
TJ Crowder

Oke ya maaf Saya perlu membaca lebih lanjut tentang internal. Anda memang harus menyalin dan tidak mengubah yang ada state.
Tikkes

0

Yang perlu Anda lakukan di sini adalah menggunakan bentuk fungsional 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]);

Kode dan kotak kerja

Bahkan jika Anda menyalin status untuk tidak mengubah yang asli, itu masih tidak dijamin bahwa Anda akan mendapatkan nilai terbaru, karena pengaturan negara menjadi async. Plus, sebagian besar metode akan mengembalikan salinan yang dangkal, jadi Anda mungkin akan bermutasi pada kondisi semula.

Menggunakan fungsional setStatememastikan bahwa Anda mendapatkan nilai negara terbaru dan tidak mengubah nilai negara asal.


"dan jangan bermutasi nilai negara asal." Saya tidak berpikir React meneruskan salinan ke callback. .sortmengubah array di tempat, jadi Anda masih harus menyalinnya sendiri.
Felix Kling

Hmm, bagus, sebenarnya saya tidak dapat menemukan konfirmasi bahwa itu melewati salinan negara, meskipun saya ingat pernah membaca sesuatu seperti itu sebelumnya.
Kejelasan

Ditemukan di sini: reactjs.org/docs/… . 'Kita dapat menggunakan bentuk pembaruan fungsional setState. Ini memungkinkan kita menentukan bagaimana keadaan perlu berubah tanpa merujuk pada keadaan saat ini '
Clarity

Saya tidak membaca itu sebagai salinan. Ini hanya berarti bahwa Anda tidak harus memberikan status saat ini sebagai ketergantungan "eksternal".
Felix Kling
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.