Best implement setState pada useEffect?

Kali ini kita akan membahas mengenai best implement dalam penggunaan setState pada useState di dalam useEffect pada React. Developer React akan sering dihadapkan dalam situasi dimana harus melakukan setState pada useEffect, seperti ketika kita ingin load data dari database dan ingin disimpan dalam sebuah state, maka kita harus mengambil data di database dan menyimpan hasil query ke dalam state dengan menggunakan useEffect. Namun implementasi ini sering kali menimbulkan masalah yang kadang tidak disadari, dan dapat memiliki dampak cukup fatal.

Implementasi setState pada useEffect

Pada kesempatan kali ini kita akan membahas tentang state dengan nilai data primitif (integer, string, boolean, dll) dan state dengan nilai data berupa object.

State dengan data primitif

Pada state yang diimplementasikan dengan menggunakan data primitif, sebenarnya tidak begitu rumit dan sudah banyak dicontohkan di berbagai forum dan dokumentasi React sendiri, berikut implementasinya:

const [state, setState] = useState(0);

State dengan data object

Kemudian untuk state yang diimplementasikan dengan menggunakan nilai object, sedikit kompleks namun cukup powerful untuk berbagai kebutuhan. Contoh state yang diinisialisasi dengan menggunakan nilai object adalah sebagai berikut:

const [state, setState] = useState({foo:null, bar:null, baz:null});

Studi Kasus

Pada pembahasan kali ini kita akan menggunakan state dengan nilai object yang lebih kompleks dari sekedar nilai primitif, harapanya apabila dapat memahami studi kasus ini, maka diharapkan dapat memahami pula state dengan nilai primitif.

const [state, setState] = useState({foo:null, bar:null, baz:null});

useEffect(() => {
    // code untuk useEffect
});

Code di atas menjelaskan bahwa kita memiliki state bernilai object dengan tiga properti: foo, bar, dan baz. Untuk melakukan update pada nilai state kita dapat menggunakan fungsi set state seperti berikut:

setState({ ...state, foo:{value} });

Sehingga, apabila kita implementasikan pada source code kita sebelumnya maka kita dapatkan sebagai berikut:

const [state, setState] = useState({foo:null, bar:null, baz:null});

useEffect(() => {
   setState({ ...state, foo:{prop: "value"} });
});

Apabila kita menerapkan code seperti di atas, maka apabila kita run, sepintas tidak ada yang aneh, namun coba perhatikan pada console log developer tools di browser kita (atau tekan tombol F12 kemudian arahkan pada jendela console). dapat diperhatikan bahwa akan terjadi error seperti berikut:

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

Error di atas dikarenakan terjadinya render secara berulang dan secara terus-menerus dan tidak terbatas, atau infinity loop. Hal ini apabila dibiarkan maka akan memakan memori dan akan berakibat fatal.

Step Solusi 1

Solusi pertama pada error tersebut adalah seperti yang dijelaskan pada dokumentasi resmi react hooks effect, bahwa kita perlu menangkap fungsi pada argumen pertama pada array di argumen kedua, berikut contohnya:

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

Perhatikan pada argumen kedua, array pada argumen kedua digunakan untuk menampung variabel apa saja yang dimonitor perubahanya, apabila variabel yang terdapat pada array tersebut berubah maka useEffect akan kembali merender function yang ada padanya.

apabila kita menggunakan IDE seperti pada Visual Studi Code, maka akan direkomendasikan variabel apa saja yang perlu ditambahkan pada array tersebut, dengan memberikan warning pada array tersebut.

baik, mari kita perbaiki function kita diatas:

const [state, setState] = useState({foo:null, bar:null, baz:null});

useEffect(() => {
   setState({ ...state, foo:{prop: "value"} });
}, []);

Dengan begini tidak akan terjadi error lagi seperti di atas. Namun perhatikan kembali, bahwa masih ada peringatan di sana (pada console developer tools):

React Hook useEffect has a missing dependency: 'state'. Either include it or remove the dependency array. You can also do a functional update 'setState(s => ...)' if you only need 'state' in the 'setState' call react-hooks/exhaustive-deps

Peringatan ini muncul karena kita disarankan untuk menambahkan state pada array argumen kedua useEffect, dengan begini, useEffect tidak akan merender kembali callback function-nya kecuali terdapat perubahan pada state. mari kita perbaiki code kita sebelumnya :

const [state, setState] = useState({foo:null, bar:null, baz:null});

useEffect(() => {
   setState({ ...state, foo:{prop: "value"} });
}, [state]);

Dengan begini kita telah menghilangkan peringatan pada console kita. namun akan terjadi error lagi seperti sebelumnya

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

Penjelasan

Error diatas adalah error yang terjadi karena terjadi perubahan dan pemantauan secara bersamaan, atau dalam context yang sama, ketika kita menggunakan …state maka kita sebenarnya sedang melakukan perubahan pada state, sedangkan state sedang dimonitor peribahanya oleh useEffect, sehingga karena terjadi perubahan pada state, useEffect kembali melakukan render pada callback function-nya, begitu dirender ulang, terjadi lagi perubahan pada state pada bagian …state, dan akan terus berulang seperti itu, maka terjadilah infinity loop pada useEffect.

Step Solusi 2

Untuk langkah penyelesaian pada error diatas, kita dapat menggunakan callback dalam melakukan setState di dalam useEffect seperti yang dijelaskan pada dokumentasi dari React sendiri. mari kita perbaiki code kita sebelumnya:

const [state, setState] = useState({foo:null, bar:null, baz:null});

useEffect(() => {
   setState(prevState => ({ ...prevState, foo:{prop: "value"} }));
}, []);

Dengan menggunakan callback pada setState, maka state tidak akan terbaca lagi oleh useEffect untuk dimonitor perubahanya, sehingga kita tidak perlu lagi menambahkan state pada array di arguman kedua. dengan begini tidak akan terjadi render secara berulang dan tidak terbatas, dan tidak akan muncul peringatan untuk menambahkan state pada array arguman kedua di useEffect.

Untuk source code pada studi kasus kali ini, dapat dilihat di bawah ini dalam format .jsx:

source code - state dengan nilai object

source code - state dengan nilai primitive

25