From 9fe2488c49e9105cd46e44b69cde60ebd0d1593b Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 11 Mar 2025 10:08:39 +0100 Subject: [PATCH 1/6] fix: console.error (#194) Use console.error instead of throwing error --- README.md | 5 +++ src/useFlagContext.test.ts | 23 +++++++++----- src/useFlagContext.ts | 65 ++++++++++++++++++++++++++++++++++---- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 647c702..e9ae783 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,11 @@ Upgrading should be as easy as running yarn again with the new version, but we m `startClient` option has been simplified. Now it will also work if you don't pass custom client with it. It defaults to `true`. +## Upgrade path from v4 -> v5 + +[FlagContext public interface changed](https://github.com/Unleash/proxy-client-react/commit/b783ef4016dbb881ac3d878cffaf5241b047cc35#diff-825c82ad66c3934257e0ee3e0511d9223db22e7ddf5de9cbdf6485206e3e02cfL20-R20). If you used FlagContext directly you may have to adjust your code slightly to accomodate the new type changes. + + #### Note on v4.0.0: The major release is driven by Node14 end of life and represents no other changes. From this version onwards we do not guarantee that this library will work server side with Node 14. diff --git a/src/useFlagContext.test.ts b/src/useFlagContext.test.ts index 8178cf3..c87a9ad 100644 --- a/src/useFlagContext.test.ts +++ b/src/useFlagContext.test.ts @@ -1,17 +1,24 @@ -import { renderHook } from '@testing-library/react-hooks/native'; +import { renderHook } from '@testing-library/react'; +import { vi, test, expect } from 'vitest'; import FlagProvider from "./FlagProvider"; import { useFlagContext } from "./useFlagContext"; -test("throws an error if used outside of a FlagProvider", () => { - const { result } = renderHook(() => useFlagContext()); +test("logs an error if used outside of a FlagProvider", () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - expect(result.error).toEqual( - Error("This hook must be used within a FlagProvider") - ); + renderHook(() => useFlagContext()); + expect(consoleSpy).toHaveBeenCalledWith("useFlagContext() must be used within a FlagProvider"); + + consoleSpy.mockRestore(); }); -test("does not throw an error if used inside of a FlagProvider", () => { +test("does not log an error if used inside of a FlagProvider", () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const { result } = renderHook(() => useFlagContext(), { wrapper: FlagProvider }); - expect(result.error).toBeUndefined(); + expect(consoleSpy).not.toHaveBeenCalled(); + expect(result.current).not.toBeNull(); + + consoleSpy.mockRestore(); }); diff --git a/src/useFlagContext.ts b/src/useFlagContext.ts index d60c83d..673e7cb 100644 --- a/src/useFlagContext.ts +++ b/src/useFlagContext.ts @@ -1,10 +1,61 @@ -import { useContext } from 'react'; -import FlagContext from './FlagContext'; +import { useContext } from "react"; +import FlagContext, { type IFlagContextValue } from "./FlagContext"; +import type { UnleashClient } from "unleash-proxy-client"; + +const methods = { + on: (event: string, callback: Function, ctx?: any): UnleashClient => { + console.error("on() must be used within a FlagProvider"); + return mockUnleashClient; + }, + off: (event: string, callback?: Function): UnleashClient => { + console.error("off() must be used within a FlagProvider"); + return mockUnleashClient; + }, + updateContext: async () => { + console.error("updateContext() must be used within a FlagProvider"); + return undefined; + }, + isEnabled: () => { + console.error("isEnabled() must be used within a FlagProvider"); + return false; + }, + getVariant: () => { + console.error("getVariant() must be used within a FlagProvider"); + return { name: "disabled", enabled: false }; + } +}; + +const mockUnleashClient = { + ...methods, + toggles: [], + impressionDataAll: {}, + context: {}, + storage: {}, + start: () => {}, + stop: () => {}, + isReady: () => false, + getError: () => null, + getAllToggles: () => [] +} as unknown as UnleashClient; + +const defaultContextValue: IFlagContextValue = { + ...methods, + client: mockUnleashClient, + flagsReady: false, + setFlagsReady: () => { + console.error("setFlagsReady() must be used within a FlagProvider"); + }, + flagsError: null, + setFlagsError: () => { + console.error("setFlagsError() must be used within a FlagProvider"); + } +}; export function useFlagContext() { - const context = useContext(FlagContext); - if (!context) { - throw new Error('This hook must be used within a FlagProvider'); - } - return context; + const context = useContext(FlagContext); + if (!context) { + console.error("useFlagContext() must be used within a FlagProvider"); + return defaultContextValue; + } + return context; } From 75e8a317b609515d2e3e7792fb366cfee329324e Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Tue, 11 Mar 2025 09:10:13 +0000 Subject: [PATCH 2/6] v5.0.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a89eb22..3d194a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/proxy-client-react", - "version": "5.0.0-rc.0", + "version": "5.0.0-rc.1", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", From 21327f540ba8a8aa5b79f48668eeab14804011c0 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 18 Mar 2025 09:11:46 +0100 Subject: [PATCH 3/6] docs: add note on v4 under the v4 migration header (#195) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e9ae783..be53ef4 100644 --- a/README.md +++ b/README.md @@ -345,13 +345,12 @@ Upgrading should be as easy as running yarn again with the new version, but we m `startClient` option has been simplified. Now it will also work if you don't pass custom client with it. It defaults to `true`. +#### Note on v4.0.0: +The major release is driven by Node14 end of life and represents no other changes. From this version onwards we do not guarantee that this library will work server side with Node 14. + ## Upgrade path from v4 -> v5 [FlagContext public interface changed](https://github.com/Unleash/proxy-client-react/commit/b783ef4016dbb881ac3d878cffaf5241b047cc35#diff-825c82ad66c3934257e0ee3e0511d9223db22e7ddf5de9cbdf6485206e3e02cfL20-R20). If you used FlagContext directly you may have to adjust your code slightly to accomodate the new type changes. - -#### Note on v4.0.0: -The major release is driven by Node14 end of life and represents no other changes. From this version onwards we do not guarantee that this library will work server side with Node 14. - ## Design philosophy This feature flag SDK is designed according to our design philosophy. You can [read more about that here](https://docs.getunleash.io/topics/feature-flags/feature-flag-best-practices). From e1e5c2827eaab0ab0eb17016a4c4e145a0aeb0b0 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Tue, 18 Mar 2025 08:16:39 +0000 Subject: [PATCH 4/6] v5.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d194a4..21e1796 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/proxy-client-react", - "version": "5.0.0-rc.1", + "version": "5.0.0", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", From 557f32d6e347de64dc5829f5292c1205d374b278 Mon Sep 17 00:00:00 2001 From: Criez's <68372390+Criezc@users.noreply.github.com> Date: Wed, 9 Apr 2025 22:18:46 +0700 Subject: [PATCH 5/6] Fix context value reference changing across rendering (#196) feat: fix context value reference change across render --- src/FlagProvider.tsx | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/FlagProvider.tsx b/src/FlagProvider.tsx index de05c05..2943ecc 100644 --- a/src/FlagProvider.tsx +++ b/src/FlagProvider.tsx @@ -1,7 +1,7 @@ /** @format */ -import React, { type FC, type PropsWithChildren, useEffect, useMemo, useState } from 'react'; -import { type IConfig, UnleashClient } from 'unleash-proxy-client'; +import React, { type FC, type PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'; +import { type IConfig, IMutableContext, UnleashClient } from 'unleash-proxy-client'; import FlagContext, { type IFlagContextValue } from './FlagContext'; export interface IFlagProvider { @@ -106,20 +106,40 @@ const FlagProvider: FC> = ({ }; }, []); + const on = useCallback(client.current.on, []); + + const off = useCallback(client.current.off, []); + + const isEnabled = useCallback( + (toggleName: string) => client.current.isEnabled(toggleName), + [] + ) + + const updateContext = useCallback( + async (context: IMutableContext) => + await client.current.updateContext(context), + [] + ) + + const getVariant = useCallback( + (toggleName: string) => client.current.getVariant(toggleName), + [] + ) + const context = useMemo( () => ({ - on: ((event, callback, ctx) => client.current.on(event, callback, ctx)) as IFlagContextValue['on'], - off: ((event, callback) => client.current.off(event, callback)) as IFlagContextValue['off'], - updateContext: async (context) => await client.current.updateContext(context), - isEnabled: (toggleName) => client.current.isEnabled(toggleName), - getVariant: (toggleName) => client.current.getVariant(toggleName), + on, + off, + updateContext, + isEnabled, + getVariant, client: client.current, flagsReady, flagsError, setFlagsReady, setFlagsError, }), - [flagsReady, flagsError] + [flagsReady, flagsError, on, off, updateContext, isEnabled, getVariant] ); return ( From b30cd252e33b4f5c55633d29ab07c496b5c87eb1 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Fri, 11 Apr 2025 21:46:53 +0200 Subject: [PATCH 6/6] revert context method changes (#201) --- src/FlagProvider.tsx | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/FlagProvider.tsx b/src/FlagProvider.tsx index 2943ecc..50331f4 100644 --- a/src/FlagProvider.tsx +++ b/src/FlagProvider.tsx @@ -1,6 +1,6 @@ /** @format */ -import React, { type FC, type PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { type FC, type PropsWithChildren, useEffect, useMemo, useState } from 'react'; import { type IConfig, IMutableContext, UnleashClient } from 'unleash-proxy-client'; import FlagContext, { type IFlagContextValue } from './FlagContext'; @@ -106,40 +106,21 @@ const FlagProvider: FC> = ({ }; }, []); - const on = useCallback(client.current.on, []); - - const off = useCallback(client.current.off, []); - - const isEnabled = useCallback( - (toggleName: string) => client.current.isEnabled(toggleName), - [] - ) - - const updateContext = useCallback( - async (context: IMutableContext) => - await client.current.updateContext(context), - [] - ) - - const getVariant = useCallback( - (toggleName: string) => client.current.getVariant(toggleName), - [] - ) - const context = useMemo( () => ({ - on, - off, - updateContext, - isEnabled, - getVariant, + on: (...args) => client.current.on(...args), + off: (...args) => client.current.off(...args), + isEnabled: (...args) => client.current.isEnabled(...args), + updateContext: async (...args) => + await client.current.updateContext(...args), + getVariant: (...args) => client.current.getVariant(...args), client: client.current, flagsReady, flagsError, setFlagsReady, setFlagsError, }), - [flagsReady, flagsError, on, off, updateContext, isEnabled, getVariant] + [flagsReady, flagsError] ); return (