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.

DanV
sumber
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.

Felix Kling
sumber
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.

Tikkes
sumber
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.

Kejelasan
sumber
"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