From 9d47258d9597f24a2ea1b2ae5fbe3f1b9c9cce7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 11 Feb 2026 14:46:38 +0800 Subject: [PATCH 1/3] refactor: add NONE state for styleReady to distinguish initial mount On initial mount when status is STATUS_NONE, return 'NONE' instead of true to prevent rendering children until style is ready. This improves the appear animation behavior by ensuring style synchronization. Co-Authored-By: Claude Opus 4.6 --- src/CSSMotion.tsx | 4 ++++ src/hooks/useStatus.ts | 11 +++++++++-- tests/CSSMotion.spec.tsx | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/CSSMotion.tsx b/src/CSSMotion.tsx index d7550c2..bb8798f 100644 --- a/src/CSSMotion.tsx +++ b/src/CSSMotion.tsx @@ -190,6 +190,10 @@ export function genCSSMotion(config: CSSMotionConfig) { // We should render children when motionStyle is sync with stepStatus return React.useMemo(() => { + if (styleReady === 'NONE') { + return null; + } + let motionChildren: React.ReactNode; const mergedProps = { ...eventProps, visible }; diff --git a/src/hooks/useStatus.ts b/src/hooks/useStatus.ts index b2fde69..f04e241 100644 --- a/src/hooks/useStatus.ts +++ b/src/hooks/useStatus.ts @@ -53,7 +53,7 @@ export default function useStatus( stepStatus: StepStatus, style: React.CSSProperties, visible: boolean, - styleReady: boolean, + styleReady: 'NONE' | boolean, ] { // Used for outer render usage to avoid `visible: false & status: none` to render nothing const [asyncVisible, setAsyncVisible] = React.useState(); @@ -311,6 +311,13 @@ export default function useStatus( step, mergedStyle, asyncVisible ?? visible, - step === STEP_START || step === STEP_ACTIVE ? styleStep === step : true, + + !mountedRef.current && currentStatus === STATUS_NONE + ? // Appear + 'NONE' + : // Enter or Leave + step === STEP_START || step === STEP_ACTIVE + ? styleStep === step + : true, ]; } diff --git a/tests/CSSMotion.spec.tsx b/tests/CSSMotion.spec.tsx index 54dc31d..dbbc490 100644 --- a/tests/CSSMotion.spec.tsx +++ b/tests/CSSMotion.spec.tsx @@ -492,6 +492,24 @@ describe('CSSMotion', () => { expect(activeBoxNode).toHaveClass(`animation-leave-active`); }); + it('styleReady returns NONE on first mount when status is STATUS_NONE', () => { + const mockRender = jest.fn(() => null); + + render( + + {mockRender} + , + ); + + expect(mockRender).toHaveBeenCalledTimes(1); + expect(mockRender).toHaveBeenCalledWith( + expect.objectContaining({ + // TODO: update with correct className + className: '', + }), + ); + }); + describe('immediately', () => { it('motionLeaveImmediately', async () => { const { container } = render( From 9a8cc73ad50bcc05f084d7fbf292d04ce214d15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 11 Feb 2026 14:52:54 +0800 Subject: [PATCH 2/3] test: update styleReady test to use correct className assertion Removed TODO and updated className assertion to verify the first render (prepare stage) with correct className value. Also changed from toHaveBeenCalled to checking mockRender.mock.calls[0][0] to match the first render call. Co-Authored-By: Claude Opus 4.6 --- tests/CSSMotion.spec.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/CSSMotion.spec.tsx b/tests/CSSMotion.spec.tsx index dbbc490..a866150 100644 --- a/tests/CSSMotion.spec.tsx +++ b/tests/CSSMotion.spec.tsx @@ -501,11 +501,10 @@ describe('CSSMotion', () => { , ); - expect(mockRender).toHaveBeenCalledTimes(1); - expect(mockRender).toHaveBeenCalledWith( + // First render (prepare stage) + expect(mockRender.mock.calls[0][0]).toEqual( expect.objectContaining({ - // TODO: update with correct className - className: '', + className: 'test-appear test-appear-prepare test', }), ); }); From 22d1d67ccb8cbe18761383ac00f0c8c5463646be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 11 Feb 2026 15:02:24 +0800 Subject: [PATCH 3/3] fix: resolve TypeScript error for jest.mock.calls access --- tests/CSSMotion.spec.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CSSMotion.spec.tsx b/tests/CSSMotion.spec.tsx index a866150..d2bb558 100644 --- a/tests/CSSMotion.spec.tsx +++ b/tests/CSSMotion.spec.tsx @@ -493,7 +493,8 @@ describe('CSSMotion', () => { }); it('styleReady returns NONE on first mount when status is STATUS_NONE', () => { - const mockRender = jest.fn(() => null); + const mockRender = jest.fn(() => null) as jest.Mock; + (mockRender as any).mock.calls = [] as any; render(