diff --git a/packages/lucide-react/src/DynamicIcon.ts b/packages/lucide-react/src/DynamicIcon.ts index 61cf04e76..b6777d60c 100644 --- a/packages/lucide-react/src/DynamicIcon.ts +++ b/packages/lucide-react/src/DynamicIcon.ts @@ -55,11 +55,21 @@ const DynamicIcon = forwardRef( const [iconData, setIconData] = useState(); useEffect(() => { + let ignore = false; + getIconData(name) - .then(setIconData) + .then((data) => { + if (!ignore) { + setIconData(data); + } + }) .catch((error) => { console.error(error); }); + + return () => { + ignore = true; + }; }, [name]); if (iconData == null) { diff --git a/packages/lucide-react/tests/DynamicIcon.spec.tsx b/packages/lucide-react/tests/DynamicIcon.spec.tsx index a99ea6c15..d7c5a584f 100644 --- a/packages/lucide-react/tests/DynamicIcon.spec.tsx +++ b/packages/lucide-react/tests/DynamicIcon.spec.tsx @@ -1,8 +1,32 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import { act, render, waitFor, type RenderResult } from '@testing-library/react'; import DynamicIcon from '../src/DynamicIcon'; +const { delayedImports, pendingImports } = vi.hoisted(() => ({ + delayedImports: new Map>(), + pendingImports: new Map>(), +})); + +vi.mock('../src/dynamicIconImports', async (importOriginal) => { + const { default: dynamicIconImports } = + await importOriginal(); + + return { + default: Object.fromEntries( + Object.entries(dynamicIconImports).map(([name, importIcon]) => [ + name, + () => { + const pendingImport = (delayedImports.get(name) ?? Promise.resolve()).then(importIcon); + pendingImports.set(name, pendingImport); + + return pendingImport; + }, + ]), + ), + }; +}); + describe('Using DynamicIcon Component', () => { it('should render icon by given name', async () => { let container: RenderResult['container']; @@ -81,4 +105,30 @@ describe('Using DynamicIcon Component', () => { expect(ref.current).toBeInstanceOf(SVGSVGElement); }); }); + + it('should render the latest icon when a previous icon finishes loading later', async () => { + let resolveCameraImport = () => {}; + delayedImports.set( + 'camera', + new Promise((resolve) => { + resolveCameraImport = resolve; + }), + ); + + const { container, rerender } = render(); + rerender(); + + await waitFor(() => { + expect(container.firstChild).toHaveClass('lucide-smile'); + }); + + await act(async () => { + resolveCameraImport(); + await pendingImports.get('camera'); + await new Promise((resolve) => setTimeout(resolve)); + }); + + expect(container.firstChild).toHaveClass('lucide-smile'); + expect(container.firstChild).not.toHaveClass('lucide-camera'); + }); });