31 Januari 2024

React key, solusi rerender uncontrolled component

Key adalah koentji

Huwaaa lama gak nulis topik tentang kode gaes. Ini kebetulan ada sekelebatan trik usang, sayang kalau cuma dibiarin lewat. Siapa tahu masih ada yang belum ngeh alias gak sadar. Daripada X Kode mulu yang update, yaudin nulis ini aja. Lanjuuuuttt… 🙃

key

Di React, fungsi key adalah untuk mengoptimalkan proses render pada daftar komponen. Misalnya ada komponen yang dirender dengan 100 iterasi. Maka setiap komponen dengan key akan dirender dan tersimpan sebagai “cache”. Sehingga jika ada render ulang, komponen tidak akan dirender, melainkan diambilkan dari yang sudah dirender sebelumnya.

// users berjumlah 100 data
users.map((user) => {
  return <User key={user.id} />;
});

Pada contoh di atas, komponen User hanya akan dirender sekali di setiap iterasi. Jika ada render ulang, komponen User tidak akan dirender lagi. Sehingga waktu yang dibutuhkan jauh lebih singkat dibanding saat pertama kali komponen User dirender, meskipun sama2 dalam 100 iterasi.

key wajib menggunakan nilai yang dapat diprediksi untuk menghasilkan optimalitas yang diharapkan.

Uncontrolled component

Uncontrolled component adalah komponen yang perilakunya tidak dikontrol oleh props. Karena tidak ada props yang mempengaruhi, maka uncontrolled component memiliki sifat yang sama seperti saat daftar komponen diberi key. Komponen tidak akan render ulang, meskipun induk komponen dirender ulang.

// uncontrolled component
<form>
  <input name="fullname" />
</form>

// controlled component
function Form() {
  const [fullname, setFullname] = useState('');

  return <form>
    <input 
      name="fullname"
      value={fullname} 
      onChange={(e) => {
        setFullname(e.target.value);
      }} 
    />
  </form>;
}

Contoh di atas cukup jelas menggambarkan perbedaan uncontrolled component dan controlled component. Uncontrolled component tidak ada props sama sekali. Sedangkan controlled component value pada input fullname dipengaruhi oleh state fullname. Dimana state fullname dikontrol oleh setFullname.

// uncontrolled component
<form 
  onSubmit={(e) => {
    // langsung kirim seluruh input, tanpa perlu state
    submit(e.currentTarget);
  }}
>
  <input name="fullname" />
</form>

// controlled component?

Nah, dengan contoh kasus di atas, akhirnya kita tahu bahwa penggunaan controlled component menjadi berlebihan. Jika kita tidak membutuhkan komponen yang bisa dikontrol, maka sudah semestinya kita tidak menggunakan controlled component. Selain tidak berguna, render ulangnya turut membebani.

Uncontrolled custom component

Tapi bagaimana dengan custom component? Perilaku custom component kan tidak sama dengan component dasar? 🤔

Betul. Mari kita tengok komponen Select dari shadcn/ui (basisnya menggunakan Radix UI) di bawah ini.

<form>
  <Select name="capres">
    <SelectTrigger>
      <SelectValue />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="abdel">Abdel</SelectItem>
      <SelectItem value="temon">Temon</SelectItem>
      <SelectItem value="muklis">Muklis</SelectItem>
    </SelectContent>
  </Select>

  <select name="capresBiasa">
    <option value="abdelBiasa">Abdel Biasa</option>
    <option value="temonBiasa">Temon Biasa</option>
    <option value="muklisBiasa">Muklis Biasa</option>
  </select>

  <button type="reset">Reset</button>
</form>

Dalam skenario reset form, komponen Select di atas tidak akan ter-reset sebagaimana komponen select biasa. Saat sudah dipilih Abdel, di-reset pun tetap akan Abdel yang terpilih.

Rerender menggunakan key

Jika pada awal tulisan saya menekankan key sebagai upaya agar proses render di daftar komponen menjadi optimal, dalam kasus reset uncontrolled custom component bisa kita manfaatkan juga gaes.

Pada kasus sebelumnya, form tidak digunakan untuk menyimpan data, tapi untuk menyaring data. Pada kasus form yang digunakan untuk menyimpan data, sederhananya kita bisa menggunakan controlled component agar custom component dapat di-reset. Tapi pada kasus menyaring data, kita akan cenderung mengirimkan data tersebut melalui query string. Maka untuk alasan efisiensi, yang kita manfaatkan adalah query string tersebut, tanpa perlu state tambahan.

<form
  onReset={() => {
    submit(initialSearch)
  }}
>
  <Select name="capres" defaultValue={search.get('capres')}>
    <SelectTrigger>
      <SelectValue />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="abdel">Abdel</SelectItem>
      <SelectItem value="temon">Temon</SelectItem>
      <SelectItem value="muklis">Muklis</SelectItem>
    </SelectContent>
  </Select>

  <select name="capresBiasa" defaultValue={search.get('capresBiasa')}>
    <option value="abdelBiasa">Abdel Biasa</option>
    <option value="temonBiasa">Temon Biasa</option>
    <option value="muklisBiasa">Muklis Biasa</option>
  </select>

  <button type="reset">Reset</button>
</form>

Pada kondisi di atas, reset yang dilakukan akan mengembalikan kondisi select biasa dalam kondisi semula, tapi tidak dengan Select. Di sini perlunya key.

<Select 
  name="capres" 
  defaultValue={search.get('capres')} 
  key={'capresKey'+search.get('capres')} // sikat gaes
>    

Sifat key kurang lebih sama seperti atribut id di element HTML. Sehingga pastikan unik, tidak boleh ada yang sama.

Dengan begitu, komponen Select tetap sebagai uncontrolled component, namun dengan kemampuan seperti controlled component dari sisi optimistik render-nya. Segala render ulang dari induk, juga akan render ulang komponen Select. Jika semua key sudah pernah di-render, maka setelahnya komponen Select bersifat seperti daftar komponen yang saya maksud di atas, tidak ada render lagi. Sehingga tetap lebih optimal daripada controlled component.


Di bawah ini saya lampirkan contoh kasus di atas ya gaes. Cukup sekian, semangat mengkode ☕️