diff --git a/packages/fiber/src/core/reconciler.tsx b/packages/fiber/src/core/reconciler.tsx index 65d16909bd..6dcf0a542e 100644 --- a/packages/fiber/src/core/reconciler.tsx +++ b/packages/fiber/src/core/reconciler.tsx @@ -337,9 +337,14 @@ function handleContainerEffects(parent: Instance, child: Instance, beforeChild?: function appendChild(parent: HostConfig['instance'], child: HostConfig['instance'] | HostConfig['textInstance']) { if (!child) return - // Link instances child.parent = parent + + // Remove existing child if it exists + const existingIndex = parent.children.indexOf(child) + if (existingIndex !== -1) parent.children.splice(existingIndex, 1) + + // Append child parent.children.push(child) // Attach tree once complete @@ -355,8 +360,14 @@ function insertBefore( // Link instances child.parent = parent - const childIndex = parent.children.indexOf(beforeChild) - if (childIndex !== -1) parent.children.splice(childIndex, 0, child) + + // Remove the child if it already exists in the parent's children array + const existingIndex = parent.children.indexOf(child) + if (existingIndex !== -1) parent.children.splice(existingIndex, 1) + + // Insert the child at the correct position + const beforeChildIndex = parent.children.indexOf(beforeChild) + if (beforeChildIndex !== -1) parent.children.splice(beforeChildIndex, 0, child) else parent.children.push(child) // Attach tree once complete diff --git a/packages/fiber/tests/renderer.test.tsx b/packages/fiber/tests/renderer.test.tsx index a3f79611a8..4130f25a29 100644 --- a/packages/fiber/tests/renderer.test.tsx +++ b/packages/fiber/tests/renderer.test.tsx @@ -782,12 +782,10 @@ describe('renderer', () => { }) it('should properly handle array of components with changing keys and order', async () => { - // Component that renders a mesh with a specific ID const MeshComponent = ({ id }: { id: number }) => { return } - // Component that maps over an array of values to render MeshComponents const Test = ({ values }: { values: number[] }) => ( <> {values.map((value) => ( @@ -796,24 +794,26 @@ describe('renderer', () => { ) - // Initial render with 4 values + // Initial render with 4 values in ascending order const initialValues = [1, 2, 3, 4] const store = await act(async () => root.render()) const { scene } = store.getState() - // Check initial state expect(scene.children.length).toBe(4) - const initialNames = scene.children.map((child) => child.name).sort() + const initialNames = scene.children.map((child) => child.name) expect(initialNames).toEqual(['mesh-1', 'mesh-2', 'mesh-3', 'mesh-4']) + expect((scene as any).__r3f.children.length).toBe(4) // Update with one less value and different order const updatedValues = [3, 1, 4] + console.log('rendering', updatedValues) await act(async () => root.render()) // Check that the scene has exactly the meshes we expect expect(scene.children.length).toBe(3) - const updatedNames = scene.children.map((child) => child.name).sort() - expect(updatedNames).toEqual(['mesh-1', 'mesh-3', 'mesh-4']) + const updatedNames = scene.children.map((child) => child.name) + expect(updatedNames).toEqual(['mesh-3', 'mesh-1', 'mesh-4']) + expect((scene as any).__r3f.children.length).toBe(3) // Verify mesh-2 was removed expect(scene.children.find((child) => child.name === 'mesh-2')).toBeUndefined() @@ -824,12 +824,14 @@ describe('renderer', () => { // Update with different order again const reorderedValues = [4, 1] + console.log('rendering', reorderedValues) await act(async () => root.render()) // Check final state expect(scene.children.length).toBe(2) - const finalNames = scene.children.map((child) => child.name).sort() - expect(finalNames).toEqual(['mesh-1', 'mesh-4']) + const finalNames = scene.children.map((child) => child.name) + expect(finalNames).toEqual(['mesh-4', 'mesh-1']) + expect((scene as any).__r3f.children.length).toBe(2) // Verify mesh-3 was removed expect(scene.children.find((child) => child.name === 'mesh-3')).toBeUndefined()