เกิดอะไรขึ้น
เรามี list ของการ์ดที่ใช้ AnimatePresence และตั้ง key เป็น index ของ array พอผู้ใช้ลบการ์ดตรงกลาง การ์ดใบสุดท้ายกลับเป็นตัวที่เล่น exit animation ส่วนการ์ดที่ถูกลบแค่เปลี่ยนเนื้อหากลายเป็นของใบข้าง ๆ
ข้อมูลถูกต้องทุกอย่าง ที่ผิดมีแค่ animation และบางครั้งก็ focus ของ input ที่อยู่ในการ์ดด้วย
ทำไมถึงเป็นแบบนั้น
React ใช้ key ตัดสินว่า element เก่าตัวไหนตรงกับตัวใหม่ เมื่อใช้ index เป็น key แล้วลบ item ที่ 2 จาก 5 ใบ key 0 ถึง 3 ยังอยู่ และ key 4 หายไป React จึงสรุปว่าใบสุดท้ายถูกลบ แล้วอัปเดต props ของใบที่เหลือแทน
AnimatePresence ทำงานตามกฎเดียวกัน คือเล่น exit animation ให้ child ที่ key หายไป exit เลยไปเล่นผิด element และด้วยเหตุผลเดียวกัน state ของ component เช่นค่าที่พิมพ์ค้างหรือ focus ก็ผูกอยู่กับตำแหน่ง ไม่ได้ผูกกับ item ส่วน list ที่ไม่เคยเรียงใหม่หรือลบ item ใช้ index เป็น key ได้ไม่มีปัญหา
ตอนนี้เราทำอย่างไร
ให้ใช้ id ที่คงที่และไม่ซ้ำจากข้อมูลเป็น key เช่น id จากฐานข้อมูล ถ้า item ไม่มี id ให้สร้างตอนสร้าง item ไม่ใช่ตอน render เพราะ key จาก Math.random() ใน render จะเปลี่ยนทุกรอบและทำให้ remount ทั้งหมด
พอใช้ key ที่คงที่ key ของ item ที่ถูกลบจะหายไปจริง AnimatePresence จึง animate ถูกตัว และ item ข้าง ๆ ก็ยังเก็บ state ของตัวเองไว้ได้
<AnimatePresence>
{items.map(item => (
<motion.li
key={item.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{item.label}
</motion.li>
))}
</AnimatePresence>เช็กลิสต์สั้น ๆ
- 1หา list ทุกตัวที่ render อยู่ใน AnimatePresence
- 2เปลี่ยน key={index} เป็น key={item.id}
- 3ถ้าไม่มี id ให้สร้างด้วย crypto.randomUUID() ตอนสร้าง item
- 4ทดสอบด้วยการลบ item ตรงกลางและเรียงลำดับใหม่
จำไว้สั้น ๆ
ใช้ id ที่คงที่จากข้อมูลเป็น key เสมอ โดยเฉพาะ list ที่ลบหรือเรียงใหม่ได้ อย่าใช้ index