From 288b62b8d5dc83e5ddc25f50aa4c21393e290839 Mon Sep 17 00:00:00 2001 From: wadii Date: Mon, 20 Apr 2026 11:44:03 +0200 Subject: [PATCH 1/2] fix: update loadingState when hydrating via serverState --- flagsmith-core.ts | 7 +++++++ test/react.test.tsx | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/flagsmith-core.ts b/flagsmith-core.ts index d81d05e..3e56587 100644 --- a/flagsmith-core.ts +++ b/flagsmith-core.ts @@ -623,6 +623,13 @@ const Flagsmith = class { this.evaluationEvent = state.evaluationEvent || this.evaluationEvent; this.identity = this.getContext()?.identity?.identifier this.log("setState called", this) + // Hydration from server state (e.g. + // without an options prop) must flip loadingState to loaded. Guarded on + // source===NONE so we never clobber a loaded state already set by + // init()/getFlags()/cache paths that also call setState internally. + if (this.loadingState.source === FlagSource.NONE && this.flags && Object.keys(this.flags).length > 0) { + this.setLoadingState(this._loadedState(null, FlagSource.SERVER)); + } } } diff --git a/test/react.test.tsx b/test/react.test.tsx index cc996e0..bdd348d 100644 --- a/test/react.test.tsx +++ b/test/react.test.tsx @@ -186,6 +186,30 @@ describe('FlagsmithProvider', () => { expect(JSON.parse(screen.getByTestId('flags').innerHTML)).toEqual(removeIds(defaultState.flags)) }) }) + it('reports a loaded state when hydrated from serverState with no options', async () => { + // Reproduces the Next.js/SSR pattern from the docs: + // + // No options prop, so flagsmith.init() is not called on the client. + // useFlagsmithLoading() must still reflect that flags are loaded, + // because the server has already provided them. + const { flagsmith } = getFlagsmith({}) + render( + + + , + ) + + // Flags are available from serverState immediately. + expect(JSON.parse(screen.getByTestId('flags').innerHTML)).toEqual(removeIds(defaultState.flags)) + + // Loading state must not be stuck on isLoading/isFetching=true. + await waitFor(() => { + const loadingState = JSON.parse(screen.getByTestId('loading-state').innerHTML) + expect(loadingState.isLoading).toBe(false) + expect(loadingState.isFetching).toBe(false) + expect(loadingState.error).toBeNull() + }) + }) it('ignores init response if identify gets called and resolves first', async () => { const onChange = jest.fn() const { flagsmith, initConfig, mockFetch } = getFlagsmith({ onChange }) From 4e078798f325a955142357f8cac5052f135f95f5 Mon Sep 17 00:00:00 2001 From: wadii Date: Mon, 20 Apr 2026 16:08:15 +0200 Subject: [PATCH 2/2] fix: removed useless comments --- test/react.test.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/react.test.tsx b/test/react.test.tsx index bdd348d..f67d1e6 100644 --- a/test/react.test.tsx +++ b/test/react.test.tsx @@ -187,11 +187,6 @@ describe('FlagsmithProvider', () => { }) }) it('reports a loaded state when hydrated from serverState with no options', async () => { - // Reproduces the Next.js/SSR pattern from the docs: - // - // No options prop, so flagsmith.init() is not called on the client. - // useFlagsmithLoading() must still reflect that flags are loaded, - // because the server has already provided them. const { flagsmith } = getFlagsmith({}) render(