Pertanyaan dan Skenario yang Berlaku
Sebuah halaman admin merender daftar pengguna yang dapat diedit. Setiap Row memiliki draf input yang belum dikirim dan state ekspansi. Pengguna dapat menyisipkan baris di bagian atas, menghapus baris tengah, mengurutkan, dan memfilter. Formulir detail di samping daftar harus mengosongkan state lokal pengguna sebelumnya setiap kali userId berubah.
Jelaskan:
- Bagaimana React memutuskan apakah suatu komponen merupakan komponen yang sama di antara render yang berdekatan.
- Mengapa
key={index}dapat memindahkan draf atau state ekspansi ke baris lain. - Mengapa
key={Math.random()}melakukan remount komponen secara berulang-ulang. - Kapan domain ID yang stabil harus mempertahankan state dan kapan mengubah key harus mereset sebuah subtree.
- Bagaimana cara memverifikasi perilaku tersebut di seluruh operasi penyisipan, penghapusan, pengurutan, pemfilteran, dan peralihan entitas.
Materi wawancara React yang diterbitkan pada Maret dan Mei 2026 secara langsung mencakup key, key indeks, rekonsiliasi, dan remounting. Meskipun pertanyaan ini tampak seperti sintaksis daftar sederhana, ini menguji apakah seorang kandidat dapat memodelkan entitas logis mana yang memiliki state dan mengekspresikan model tersebut melalui batasan komponen dan pengujian.
Ini adalah pertanyaan frontend karena kompetensi intinya adalah identitas komponen React, masa hidup state, dan penggunaan kembali DOM.
Apa yang Sedang Dievaluasi oleh Pewawancara
Pertama, kandidat harus mengetahui bahwa state tidak secara otomatis terikat pada tag JSX atau objek domain. React mengaitkan state dengan posisi di dalam render tree. Di dalam satu induk (parent), tipe elemen dan key membantu menentukan apakah turunan lama dan baru merepresentasikan identitas yang sama.
Kedua, kandidat harus membedakan antara re-render dan remount. Props baru atau pembaruan parent dapat melakukan re-render pada identitas yang sama sambil mempertahankan state lokalnya. Tipe komponen atau key yang berubah akan menciptakan identitas baru, menghancurkan state lokal subtree lama, dan mungkin membuat ulang DOM-nya.
Ketiga, kandidat harus menurunkan key dari identitas domain. ID database atau UUID yang dibuat dan disimpan saat record dibuat biasanya berarti "ini masih record yang sama." Posisi array, timestamp saat ini, atau nilai acak yang dihasilkan selama proses render tidak merepresentasikan hal tersebut.
Keempat, jawaban yang kuat tidak menjadikan "jangan pernah mengubah key" sebagai aturan mutlak. Ketika formulir detail beralih dari pengguna A ke pengguna B, formulir tersebut dapat mewakili entitas domain yang berbeda. key={userId} pada batasan yang tepat dapat mereset seluruh subtree formulir dengan lebih andal daripada mengosongkan variabel state individual di dalam Effect.
Terakhir, kandidat harus menyebutkan konsekuensi dari remounting: fokus, posisi scroll, input yang belum dikirim, dan state turunan semuanya bisa hilang. Jika produk harus memulihkan draf saat suatu entitas dipilih kembali, angkat (lift) draf tersebut, simpan berdasarkan entitas, atau simpan secara eksternal alih-alih bergantung pada state lokal di subtree yang telah dihapus.
Pertanyaan Klarifikasi Sebelum Menjawab
- Bisakah daftar tersebut menyisipkan, menghapus, mengurutkan, atau memfilter item? Begitu keanggotaan atau urutan dapat berubah, indeks array tidak lagi mengidentifikasi entitas domain secara stabil.
- Apakah setiap baris memiliki state React atau DOM browser? Input, state ekspansi, animasi, dan field yang tidak terkontrol (uncontrolled) mengekspos penggunaan kembali yang salah dengan cepat.
- Apakah data memiliki ID stabil yang unik di antara saudaranya (siblings)? Key membutuhkan keunikan tingkat sibling, bukan keunikan global.
- Haruskah peralihan entitas membuang atau memulihkan drafnya? Membuang draf cocok dengan perubahan key; pemulihan memerlukan lapisan state yang hidup lebih lama.
- Haruskah seluruh subtree direset atau hanya satu field? Gunakan key untuk reset identitas secara menyeluruh; pilih controlled state atau pembaruan data eksplisit untuk penyesuaian parsial.
- Kapan ID tersebut dibuat? Record lokal dapat menerima UUID saat dibuat dan menyimpannya. Jangan membuat yang baru pada setiap render.
Kerangka Jawaban 30 Detik
"React mengaitkan state dengan posisi di dalam render tree. Di bawah parent yang sama, tipe komponen dan key membantu React menentukan apakah node lama dan baru memiliki identitas yang sama. Key yang stabil memungkinkan state baris mengikuti record domain meskipun posisinya berpindah. Dengan indeks array, penyisipan atau pengurutan dapat menempatkan record yang berbeda di slot yang sama, sehingga draf lokal dapat berpindah ke baris yang salah. Key acak tidak akan pernah cocok dengan render sebelumnya, sehingga React berulang kali membuat ulang komponen dan DOM.
Daftar harus menggunakan ID stabil dari data. Jika beralih dari formulir detail pengguna A ke pengguna B harus mengosongkan semua state lokal, saya akan meletakkan key={userId} pada batasan subtree formulir untuk menyatakan bahwa ini adalah entitas baru. Jika draf setiap pengguna harus bertahan, saya akan mengangkat (lift) draf dan menyimpannya berdasarkan userId sambil tetap menjaga batasan identitas. Saya akan memverifikasi penyisipan, penghapusan, pengurutan ulang, pemfilteran, re-render dengan ID yang sama, dan peralihan ID yang berbeda untuk membuktikan bahwa state mengikuti entitas yang dimaksud."
Pembahasan Mendalam Langkah demi Langkah
Langkah 1: Bangun model identitas komponen.
Model wawancara yang berguna adalah:
| Hubungan antara render lama dan baru | Hasil umum |
|---|---|
| Parent yang sama, tipe komponen yang sama, key yang sama | Pertahankan state lokal dari identitas tersebut; komponen dapat melakukan re-render |
| Parent yang sama, tipe yang sama, key yang berbeda | Hapus identitas lama, mount identitas baru, dan reset state subtree |
| Posisi yang sama, tipe komponen yang berbeda | Ganti subtree lama dan reset state |
| Daftar tanpa key eksplisit | Kembali ke pencocokan posisi (positional matching), yang tidak aman untuk daftar dinamis |
key bukanlah prop biasa yang diteruskan ke komponen. Ini adalah petunjuk (hint) untuk React. Jika Row juga memerlukan domain ID, teruskan prop terpisah seperti rowId={item.id}.
Key juga mendefinisikan identitas hanya di dalam parent saat ini. Dua daftar yang terpisah dapat sama-sama berisi key="user-42", sedangkan siblings di dalam satu daftar tidak boleh memiliki key duplikat.
Langkah 2: Reproduksi bug index-key dengan daftar yang dapat diedit.
Implementasi yang salah:
function UserList({ users }: { users: User[] }) {
return users.map((user, index) => (
<EditableRow key={index} user={user} />
))
}Asumsikan urutan awal adalah [Alice, Bob]. EditableRow pada indeks 0 menyimpan draf Alice. Sisipkan Zoe di bagian atas, menghasilkan [Zoe, Alice, Bob]. React dapat mencocokkan baris lama dengan key 0 ke item baru pada indeks 0, sehingga state milik Alice mungkin muncul di baris Zoe. State pada key 1 dapat berpindah serupa dari Bob ke Alice.
Masalahnya bukan karena indeks adalah sebuah angka. Indeks mengidentifikasi sebuah slot, sedangkan produk perlu mengidentifikasi pengguna. Penghapusan, pengurutan, dan pemfilteran semuanya mengubah pemetaan antara slot dan entitas.
Implementasi yang benar:
function UserList({ users }: { users: User[] }) {
return users.map((user) => (
<EditableRow key={user.id} rowId={user.id} user={user} />
))
}Setelah Zoe disisipkan, Alice tetap memiliki ID Alice. Dia dapat berpindah dari indeks 0 ke indeks 1, dan React dapat terus mencocokkan state komponen Alice dengan Alice.
Jika satu record mengembalikan beberapa node sibling, sintaksis Fragment pendek tidak dapat menerima key. Gunakan Fragment eksplisit:
import { Fragment } from 'react'
users.map((user) => (
<Fragment key={user.id}>
<UserHeading user={user} />
<EditableRow user={user} />
</Fragment>
))Langkah 3: Pilih key yang stabil alih-alih membuatnya saat proses render.
Urutan preferensi praktisnya adalah:
- ID record yang stabil dari backend atau database.
- Pengenal unik yang sudah dilampirkan pada data dan tidak berubah sepanjang masa pakai domainnya.
- Untuk record baru yang hanya ada di lokal, UUID yang dibuat dalam event pembuatan record dan disimpan bersama record tersebut.
- Composite key hanya jika field-fieldnya benar-benar membentuk identitas domain yang immutable dan unik di antara siblings.
Berikut ini menciptakan identitas baru pada setiap render:
<EditableRow key={Math.random()} user={user} />Ini bukan re-render biasa. React menghapus baris lama dan me-mount baris baru. State lokal dan input pengguna hilang, dan DOM dibuat ulang. Date.now() atau memanggil crypto.randomUUID() saat render memiliki masalah yang sama. UUID aman digunakan jika dibuat sekali saat pembuatan item dan disimpan, bukan saat merender item tersebut.
Indeks array hanya dapat diterima di bawah kontrak yang ketat: keanggotaan dan urutan tetap permanen sepanjang masa hidup daftar, tidak ada penyisipan, penghapusan, pemfilteran, atau pengurutan ulang, posisi itu sendiri adalah identitasnya, dan tidak ada state yang harus mengikuti entitas domain. Data bisnis yang dinamis harus menerima ID asli daripada mengandalkan asumsi-asumsi tersebut.
Langkah 4: Gunakan key untuk menyatakan "ini adalah formulir yang berbeda."
Upaya perbaikan yang umum dilakukan pada halaman detail adalah:
function Profile({ userId }: { userId: string }) {
const [comment, setComment] = useState('')
useEffect(() => {
setComment('')
}, [userId])
return <CommentForm value={comment} onChange={setComment} />
}Ini pertama-tama merender tree dengan komentar lama yang basi dan kemudian memicu render lain setelah Effect berjalan. Lebih penting lagi, halaman detail yang dalam juga dapat berisi lampiran, error validasi, dan state formulir bersarang. Mengosongkan satu variabel comment tidak menjamin bahwa seluruh subtree akan direset.
Jika produk mendefinisikan formulir setiap pengguna sebagai entitas yang berbeda, letakkan key pada batasan identitas:
function ProfilePage({ userId }: { userId: string }) {
return <ProfileForm key={userId} userId={userId} />
}Ketika userId berubah, React memperlakukan ProfileForm baru sebagai identitas yang berbeda dan mereset state lokalnya beserta semua state turunan. Ketika state parent yang tidak terkait menyebabkan re-render dengan userId yang sama, key tetap stabil dan state lokal dipertahankan.
Tempatkan key pada batasan terkecil yang lengkap yang harus direset. Memberi key pada seluruh halaman juga akan membuat ulang navigasi, tampilan yang berat, dan state scroll yang tidak terkait. Memberi key hanya pada satu input dapat meninggalkan state lain dari formulir yang sama.
Langkah 5: Tentukan apa yang harus dipertahankan produk sebelum memilih remount.
"Beralih entitas" tidak secara otomatis berarti "buang draf." Gunakan tabel keputusan:
| Semantik produk | Desain state yang direkomendasikan |
|---|---|
| Entitas lain harus menerima formulir yang benar-benar baru | Gunakan ID entitas sebagai key subtree formulir |
| Kembali ke suatu entitas harus memulihkan drafnya | Pertahankan key identitas; angkat (lift) draf dan simpan berdasarkan ID |
| Refresh halaman juga harus memulihkan draf | Simpan draf secara eksternal dengan aturan kedaluwarsa dan pembersihan |
| Reset satu field sambil mempertahankan field lainnya | Gunakan controlled state atau pembaruan eksplisit; jangan me-remount subtree |
| Props hanya mengubah nilai tampilan turunan (derived) | Hitung dari props selama render alih-alih menyalinnya ke dalam state |
Key mendefinisikan batasan identitas; key tidak menyediakan persistensi jangka panjang. Setelah state diangkat ke drafts[userId], subtree formulir dapat di-unmount sementara drafnya tetap berada di parent. Memilih pengguna itu lagi dapat menginisialisasi atau mengontrol formulir dengan nilai yang disimpan.
Langkah 6: Kenali penyebab lain dari reset yang tidak disengaja.
Bahkan dengan key yang benar, mengubah tipe komponen akan mereset state. Mengubah posisi tree yang sama dari ProfileForm ke LoginPrompt akan menggantikan subtree tersebut.
Kesalahan umum lainnya adalah mendefinisikan komponen di dalam komponen lain:
function ProfilePage() {
function ProfileForm() {
const [name, setName] = useState('')
return <input value={name} onChange={(event) => setName(event.target.value)} />
}
return <ProfileForm />
}Setiap render ProfilePage akan membuat objek fungsi ProfileForm yang baru. React melihat tipe komponen yang berbeda dan secara tidak terduga mereset state input. Definisi komponen harus tetap berada di tingkat atas (top level). Jawaban yang hanya berfokus pada key dapat melewatkan bug identitas terkait ini.
Langkah 7: Terapkan aturan identitas yang sama pada daftar ter-virtualisasi (virtualized lists).
Daftar ter-virtualisasi menggunakan kembali sejumlah kecil slot yang terlihat secara berulang-ulang. Jika library menerima itemKey, kembalikan domain entity ID daripada indeks jendela (window index). Jika tidak, scroll atau pengurutan ulang dapat membiarkan satu entitas mewarisi state dari slot lain.
Untuk daftar besar, desain yang lebih aman sering kali mengurangi state bisnis yang tidak persisten di dalam baris. Simpan draf edit di atas daftar berdasarkan ID record dan biarkan setiap baris membaca draf entitasnya. Dengan demikian, library virtualisasi dapat meng-unmount baris di luar layar tanpa menghapus data bisnis.
Langkah 8: Verifikasi kepemilikan state, bukan hanya teks yang dirender.
Untuk setiap pengujian, nyatakan ID mana yang harus memiliki state tersebut:
| Operasi | Hasil yang diharapkan |
|---|---|
| Ketik draf di Alice, lalu sisipkan Zoe di bagian atas | Draf masih hanya milik Alice |
| Hapus item di tengah | State ekspansi dan input pada baris lain tidak bermigrasi |
| Urutkan menurun, lalu kembalikan ke urutan awal | State setiap baris terus mengikuti record ID-nya |
| Filter Alice hingga hilang, lalu pulihkan filter | State lokal baris hilang setelah unmount; pulihkan dari penyimpanan draf yang diangkat jika diperlukan |
| Picu re-render parent dengan userId yang sama | State lokal formulir dipertahankan |
| Beralih dari pengguna A ke pengguna B | Formulir dengan key userId direset sepenuhnya |
| Gunakan key acak untuk sementara dan picu re-render parent | Kehilangan input dan pembuatan ulang DOM mendemonstrasikan mekanisme kegagalan |
| Menerima ID duplikat | Berikan peringatan atau tolak pada batasan data untuk menghindari konflik key sibling |
Pengujian juga harus mencakup fokus dan input yang tidak terkontrol. Key yang salah dapat membuat state React terlihat normal sementara nilai DOM yang dipegang browser atau fokus berpindah ke record yang salah. Kriteria penerimaan harus menggambarkan kontinuitas entitas yang terlihat oleh pengguna, bukan hanya jumlah render.
Contoh Jawaban Berkualitas Tinggi
"Saya memperlakukan key sebagai bagian dari identitas komponen, bukan hanya atribut untuk menghilangkan peringatan konsol. React mengaitkan state dengan posisi render-tree. Di bawah satu parent, tipe komponen dan key yang sama biasanya mewakili identitas yang sama, sehingga pembaruan prop atau perpindahan posisi dapat memicu re-render sambil mempertahankan state. Tipe atau key yang berubah mewakili identitas baru: React menghapus subtree lama dan me-mount yang baru.
Dalam daftar yang dapat diedit, indeks mengidentifikasi posisi, bukan pengguna. Jika [Alice, Bob] menggunakan key 0 dan 1 lalu Zoe disisipkan di atas, baris lama dengan key 0 sekarang dapat merender Zoe, sehingga draf atau state ekspansi Alice dapat muncul di bawah Zoe. Saya akan menggunakan user.id, yang menjaga identitas Alice tetap stabil saat dia berpindah dari indeks 0 ke indeks 1. Key acak, timestamp, atau UUID yang dibuat selama render tidak pernah cocok dengan render sebelumnya, sehingga React berulang kali membuat ulang komponen dan DOM serta kehilangan input. Key hanya perlu unik di antara siblings, dan child tidak menerima key sebagai prop.
Formulir detail bergantung pada semantik produk. Jika beralih dari pengguna A ke pengguna B harus mengosongkan seluruh formulir, saya akan meletakkan key={userId} pada batasan ProfileForm sehingga semua state turunan direset bersamaan alih-alih mengosongkan field di beberapa Effect. Jika kembali ke A harus memulihkan draf, saya akan menjaga identitas tetap terpisah berdasarkan userId tetapi mengangkat (lift) atau menyimpan draf secara eksternal berdasarkan userId.
Terakhir, saya akan menguji penyisipan di bagian atas, penghapusan, pengurutan ulang, pemfilteran, re-render dengan ID yang sama, dan peralihan ID yang berbeda. Saya akan memeriksa bahwa input, state ekspansi, fokus, dan draf semuanya mengikuti domain ID yang dimaksud. Itu membuktikan bahwa key memodelkan identitas yang benar daripada sekadar memungkinkan halaman untuk diperbarui."
Kesalahan Umum
- Menjelaskan key hanya sebagai optimasi performa → Masalah intinya adalah identitas turunan dan kepemilikan state → Jelaskan ketidaksesuaian state terlebih dahulu, baru kemudian biaya pembaruan.
- Menggunakan indeks array untuk setiap daftar → Penyisipan, penghapusan, pengurutan ulang, dan pemfilteran mengubah pemetaan slot-ke-entitas → Gunakan ID stabil dari data.
- Menghasilkan UUID atau nilai acak selama proses render → Key berubah setiap saat dan membuat ulang komponen serta DOM → Buat dan simpan ID saat item pertama kali dibuat.
- Mengharuskan key unik secara global → React hanya membutuhkan keunikan di antara siblings → Evaluasi konflik di dalam parent dan daftar saat ini.
- Membaca
props.keydi dalam child → React tidak meneruskan key sebagai prop biasa → TeruskanrowIdatauuserIdsecara terpisah. - Mengosongkan formulir kompleks field demi field di dalam Effect → Ini merender state lama yang basi, menambah render tambahan, dan dapat melewatkan state turunan → Ubah key pada batasan identitas yang benar.
- Me-remount seluruh halaman untuk mengosongkan satu field → Ini menghilangkan fokus, scroll, dan state yang tidak terkait → Persempit batasan key atau perbarui field secara eksplisit.
- Mengasumsikan key yang stabil mempertahankan state setelah pemfilteran → Komponen yang difilter mungkin telah di-unmount → Angkat (lift) atau simpan state jika pemulihan diperlukan.
Pertanyaan Lanjutan
Lanjutan 1: Key apa yang harus digunakan jika data tidak memiliki ID dari backend?
Buat ID, seperti UUID, dalam event yang membuat record lokal dan simpan sebagai field record. Setiap render berikutnya akan membaca ID yang sama. Kombinasi field domain yang immutable dan unik di tingkat sibling dapat berfungsi jika kontrak tersebut terbukti. Jangan membuat key secara sementara di dalam proses render map.
Lanjutan 2: Kapan indeks array dapat diterima sebagai key?
Ini relatif aman hanya jika keanggotaan dan urutan tetap permanen sepanjang masa hidup daftar, tidak ada penyisipan, penghapusan, pemfilteran, atau pengurutan ulang, dan posisi itu sendiri adalah identitasnya. Tampilan statis yang tetap dapat memenuhi batasan tersebut. Data bisnis dinamis harus menggunakan ID entitas yang stabil karena pengurutan atau pengeditan akan langsung membatalkan asumsi indeks.
Lanjutan 3: Jika hanya satu field yang harus dibersihkan saat userId berubah, haruskah seluruh key formulir diubah?
Tidak perlu. Key akan mereset seluruh subtree. Jika state lokal lainnya harus tetap ada, gunakan controlled field, turunkan nilai secara langsung dari props, atau sesuaikan dalam jalur pembaruan data eksplisit. Pilih reset key hanya ketika seluruh subtree mewakili entitas lain dan semua state lokalnya harus dimulai dari awal.
Lanjutan 4: Bagaimana cara memulihkan draf setelah mengubah key?
Angkat (lift) draf ke parent, misalnya sebagai drafts[userId], atau simpan secara eksternal dengan aturan kedaluwarsa dan pembersihan. Jaga agar key formulir tetap didasarkan pada userId sehingga pengguna yang berbeda tidak berbagi state lokal. Formulir yang baru di-mount akan membaca nilai awalnya dari draf tersimpan yang sesuai. Isolasi identitas dan persistensi draf adalah dua tanggung jawab yang terpisah.
Lanjutan 5: Apakah daftar ter-virtualisasi masih memerlukan key yang stabil jika sudah menggunakan kembali DOM?
Ya. Virtualisasi mengontrol jumlah node yang terlihat; itu tidak mendefinisikan ulang identitas domain. Jika library menyediakan itemKey, kembalikan record ID. State pengeditan yang harus bertahan dari proses unmount juga harus berada di atas baris berdasarkan ID, atau state tersebut akan tetap hilang saat baris keluar dari area pandang layar (window).