From e6332b3a17897b3ebeed5408b3b23d8172c6c880 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Fri, 17 Jul 2026 13:13:43 +0200 Subject: [PATCH 1/2] fix(react-sdk): respect initial loading through initialization --- packages/react-sdk/src/index.tsx | 16 +++++++- packages/react-sdk/test/usage.test.tsx | 56 +++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/packages/react-sdk/src/index.tsx b/packages/react-sdk/src/index.tsx index a7a28a7c..661ac91b 100644 --- a/packages/react-sdk/src/index.tsx +++ b/packages/react-sdk/src/index.tsx @@ -271,14 +271,26 @@ export function ReflagClientProvider({ initialLoading = true, children, }: ReflagClientProviderProps) { + const hasInitialized = useRef(client.getState() === "initialized"); const [isLoading, setIsLoading] = useState( - client.getState() !== "initialized" ? initialLoading : false, + hasInitialized.current ? false : initialLoading, ); useOnEvent( "stateUpdated", (state) => { - setIsLoading(state === "initializing"); + if (state === "initialized") { + hasInitialized.current = true; + setIsLoading(false); + return; + } + + if (state === "initializing") { + setIsLoading(hasInitialized.current || initialLoading); + return; + } + + setIsLoading(false); }, client, ); diff --git a/packages/react-sdk/test/usage.test.tsx b/packages/react-sdk/test/usage.test.tsx index f3f69f4a..3a7c87d9 100644 --- a/packages/react-sdk/test/usage.test.tsx +++ b/packages/react-sdk/test/usage.test.tsx @@ -711,6 +711,57 @@ describe("", () => { // Removed test "does not initialize when no flags are provided" // because ReflagBootstrappedProvider requires flags to be provided + test("does not enter UI loading during initial bootstrap initialization", async () => { + const bootstrapFlags: BootstrappedFlags = { + context: { user, company, other }, + flags: { + abc: { + key: "abc", + isEnabled: true, + targetingVersion: 1, + }, + }, + }; + let resolveStorageRead: (value: string | null) => void; + const storageRead = new Promise((resolve) => { + resolveStorageRead = resolve; + }); + const storage = { + getItem: vi.fn(() => storageRead), + setItem: vi.fn(async () => undefined), + }; + let client: ReflagClient | undefined; + + function Content() { + client = useClient(); + return ( + {String(useIsLoading())} + ); + } + + const { queryByTestId, unmount } = render( + getBootstrapProvider(bootstrapFlags, { + storage, + loadingComponent: Loading..., + children: , + }), + ); + + await waitFor(() => { + expect(client?.getState()).toBe("initializing"); + }); + + expect(queryByTestId("loading")).toBeNull(); + expect(queryByTestId("bootstrap-content")?.textContent).toBe("false"); + + resolveStorageRead!(null); + await waitFor(() => { + expect(client?.getState()).toBe("initialized"); + }); + + unmount(); + }); + test("shows content after initialization", async () => { const bootstrapFlags: BootstrappedFlags = { context: { @@ -1217,7 +1268,10 @@ describe("useIsLoading", () => { const initializeSpy = vi .spyOn(ReflagClient.prototype, "initialize") - .mockResolvedValue(undefined); + .mockImplementationOnce(async function () { + (this as any).hooks.trigger("stateUpdated", "initializing"); + (this as any).hooks.trigger("stateUpdated", "initialized"); + }); let resolveSetContext: (() => void) | undefined; const setContextPromise = new Promise((resolve) => { resolveSetContext = resolve; From 39bd8d81f962238be0a39127f66be69ef4b09292 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Fri, 17 Jul 2026 15:12:50 +0200 Subject: [PATCH 2/2] changeset --- .changeset/tired-icons-cheat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tired-icons-cheat.md diff --git a/.changeset/tired-icons-cheat.md b/.changeset/tired-icons-cheat.md new file mode 100644 index 00000000..3e88924e --- /dev/null +++ b/.changeset/tired-icons-cheat.md @@ -0,0 +1,5 @@ +--- +"@reflag/react-sdk": patch +--- + +Avoid loading state during bootstrapped initialization