From 1d1d3aa02b9d852400fda678c52b73f78529c888 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Thu, 31 Oct 2024 13:47:14 +0100 Subject: [PATCH 01/21] fix: avoid startTransition in React Native (#178) --- src/FlagProvider.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/FlagProvider.tsx b/src/FlagProvider.tsx index 3e5a742..de05c05 100644 --- a/src/FlagProvider.tsx +++ b/src/FlagProvider.tsx @@ -9,6 +9,7 @@ export interface IFlagProvider { unleashClient?: UnleashClient; startClient?: boolean; stopClient?: boolean; + startTransition?: (fn: () => void) => void; } const offlineConfig: IConfig = { @@ -23,7 +24,8 @@ const offlineConfig: IConfig = { // save startTransition as var to avoid webpack analysis (https://github.com/webpack/webpack/issues/14814) const _startTransition = 'startTransition'; // fallback for React <18 which doesn't support startTransition -const startTransition = React[_startTransition] || (fn => fn()); +// Fallback for React <18 and exclude startTransition if in React Native +const defaultStartTransition = React[_startTransition] || (fn => fn()); const FlagProvider: FC> = ({ config: customConfig, @@ -31,6 +33,7 @@ const FlagProvider: FC> = ({ unleashClient, startClient = true, stopClient = true, + startTransition = defaultStartTransition }) => { const config = customConfig || offlineConfig; const client = React.useRef( From 1dd2c690cedfbb80aa9c3060f2e3ba395fc869b5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 31 Oct 2024 12:49:30 +0000 Subject: [PATCH 02/21] v4.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eec2698..d55703c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/proxy-client-react", - "version": "4.3.1", + "version": "4.4.0", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", From dad10dfc97491465caf1b71830fb3d7c8fdb48a5 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 29 Nov 2024 10:31:48 +0100 Subject: [PATCH 03/21] docs: startTransition in React Native (#181) --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index ce2078f..bde1e50 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,8 @@ Wrap your components like so: ## React Native +### localStorage + IMPORTANT: This no longer comes included in the unleash-proxy-client-js library. You will need to install the storage adapter for your preferred storage solution. Because React Native doesn't run in a web browser, it doesn't have access to the `localStorage` API. Instead, you need to tell Unleash to use your specific storage provider. The most common storage provider for React Native is [AsyncStorage](https://github.com/react-native-async-storage/async-storage). @@ -282,6 +284,13 @@ const config = { }; ``` +### startTransition + +If your version of React Native doesn't support `startTransition`, you can provide fallback implementation: +```jsx + fn()} > +``` + # Migration guide ## Upgrade path from v1 -> v2 From 87555d168429e5d0e588609870e16d3b9497ddaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:00:23 +0100 Subject: [PATCH 04/21] chore(deps): bump nanoid from 3.3.6 to 3.3.8 (#182) Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.6 to 3.3.8. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.3.6...3.3.8) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c00d58d..27b2502 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1545,9 +1545,9 @@ muggle-string@^0.3.1: integrity sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg== nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== node-releases@^2.0.12: version "2.0.12" From 4dde7bf8770da24278c9b515cff051fe42c84cae Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 14 Jan 2025 15:45:41 +0100 Subject: [PATCH 05/21] Docs/document events (#184) --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index bde1e50..ae41aa8 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,12 @@ yarn add @unleash/proxy-client-react unleash-proxy-client # How to use +This library uses the core [unleash-proxy-client](https://github.com/Unleash/unleash-proxy-client-js) JS client as a base. + ## Initialize the client +*NOTE*: [unleash-proxy](https://github.com/Unleash/unleash-proxy) is in maintenance mode. It is recommend to use the [Frontend API](https://docs.getunleash.io/reference/front-end-api) or [unleash-edge](https://github.com/Unleash/unleash-edge) instead. + Prepare [Unleash Proxy](https://docs.getunleash.io/reference/unleash-proxy) secret or [Frontend API Access](https://docs.getunleash.io/reference/front-end-api) token. @@ -42,6 +46,8 @@ root.render( To connect this SDK to your Unleash instance's [front-end API](https://docs.getunleash.io/reference/front-end-api), use the URL to your Unleash instance's front-end API (`/api/frontend`) as the `url` parameter. For the `clientKey` parameter, use a `FRONTEND` token generated from your Unleash instance. Refer to the [_how to create API tokens_](https://docs.getunleash.io/how-to/how-to-create-api-tokens) guide for the necessary steps. +To connect this SDK to unleash-edge, follow the documentation provided in the [unleash-edge repository](https://github.com/unleash/unleash-edge). + To connect this SDK to the [Unleash proxy](https://docs.getunleash.io/reference/unleash-proxy), use the proxy's URL and a [proxy client key](https://docs.getunleash.io/reference/api-tokens-and-client-keys#proxy-client-keys). The [_configuration_ section of the Unleash proxy docs](https://docs.getunleash.io/reference/unleash-proxy#configuration) contains more info on how to configure client keys for your proxy. ## Check feature toggle status @@ -134,6 +140,36 @@ const MyComponent = ({ userId }) => { }; ``` +## Listening to events + +The core JavaScript client emits various types of events depending on internal activities. You can listen to these events by using a hook to access the client and then directly attaching event listeners. Alternatively, if you're using the FlagProvider with a client, you can directly use this client to listen to the events. [See the full list of events here.](https://github.com/Unleash/unleash-proxy-client-js?tab=readme-ov-file#available-events) + +NOTE: `FlagProvider` uses these internal events to provide information through `useFlagsStatus`. + +```jsx +import { useUnleashContext, useFlag } from '@unleash/proxy-client-react'; + +const MyComponent = ({ userId }) => { + const client = useUnleashClient(); + + useEffect(() => { + if (client) { + const handleError = () => { + // Handle error + } + + client.on('error', handleError) + } + + return () => { + client.off('error', handleError) + } + }, [client]) + + // ...rest of component +}; +``` + # Advanced use cases ## Deferring client start From a3e78363246e58eee74b822cb7bed0e9b893d7b1 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Wed, 15 Jan 2025 13:41:20 +0100 Subject: [PATCH 06/21] Meta: update release template to use npm release v2 (#186) Updates the release template to use the new npm release v2 action, which relies on the GitHub bot instead of a personal PAT. --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5af9618..cded89c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,10 +15,11 @@ on: jobs: from-template: - uses: Unleash/.github/.github/workflows/npm-release.yml@v1.1.0 + uses: Unleash/.github/.github/workflows/npm-release.yml@v2.0.0 with: version: ${{ github.event.inputs.version }} tag: ${{ github.event.inputs.tag }} secrets: - GH_ACCESS_TOKEN: ${{ secrets.GH_TOKEN}} NPM_ACCESS_TOKEN: ${{ secrets.NPM_TOKEN }} + UNLEASH_BOT_APP_ID: ${{ secrets.UNLEASH_BOT_APP_ID }} + UNLEASH_BOT_PRIVATE_KEY: ${{ secrets.UNLEASH_BOT_PRIVATE_KEY }} From 5d59503bc308f22ada362771a13ab2a06741f456 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 16 Jan 2025 08:22:37 +0100 Subject: [PATCH 07/21] feat: add sdk uniqueness headers (#185) Updates the dependency on the underlying Unleash client to 3.7.1. This version of the client adds new x-unleash headers which lets Unleash count unique connections and see SDK version information. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d55703c..a596b78 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "react-dom": "^18.2.0", "react-test-renderer": "^18.2.0", "typescript": "^5.3.2", - "unleash-proxy-client": "^3.5.2", + "unleash-proxy-client": "^3.7.1", "vite": "^4.5.0", "vite-plugin-dts": "^3.6.3", "vitest": "^0.34.6" diff --git a/yarn.lock b/yarn.lock index 27b2502..c6d81d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1972,10 +1972,10 @@ universalify@^0.2.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== -unleash-proxy-client@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.5.2.tgz#e5c1735e94ada7abec33958e2b252ba66ad83074" - integrity sha512-fbxTmNyJ/B6uKAZSRcfzZ9IXHokPikWgI14/6DQU3poJjZr+P7hX2KyZbkucd1/0VFYWnNTPAn+ihwyV3C8F/Q== +unleash-proxy-client@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.7.1.tgz#c51483aebaad664e6d6ea70828f5f10bece61a40" + integrity sha512-ri3cauGfQBjvRvwwJIQfnHlpOfFvQz0iy5hVd82Tr4t0LkqpqFr248tuO8jkI39JXelUcX7TCGNHneeMMPZM1A== dependencies: tiny-emitter "^2.1.0" uuid "^9.0.1" From 971ab39d11b2ba72ff76ea9c6a0d9aa598046d51 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 16 Jan 2025 10:15:10 +0000 Subject: [PATCH 08/21] v4.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a596b78..49a9fc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/proxy-client-react", - "version": "4.4.0", + "version": "4.5.0", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", From 6574bf697ba432da60ce6831725a034bf6f5be31 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 16 Jan 2025 13:56:03 +0100 Subject: [PATCH 09/21] fix: update proxy client dep to 3.7.2 (#187) * fix: update proxy client dep to 3.7.2 This update includes uuid generation that doesn't rely on the `crypto` module for connection id. * fix: also update peer dep --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 49a9fc8..04a4733 100644 --- a/package.json +++ b/package.json @@ -50,12 +50,12 @@ "react-dom": "^18.2.0", "react-test-renderer": "^18.2.0", "typescript": "^5.3.2", - "unleash-proxy-client": "^3.7.1", + "unleash-proxy-client": "^3.7.2", "vite": "^4.5.0", "vite-plugin-dts": "^3.6.3", "vitest": "^0.34.6" }, "peerDependencies": { - "unleash-proxy-client": "^3.5.2" + "unleash-proxy-client": "^3.7.2" } } diff --git a/yarn.lock b/yarn.lock index c6d81d0..97fb114 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1972,10 +1972,10 @@ universalify@^0.2.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== -unleash-proxy-client@^3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.7.1.tgz#c51483aebaad664e6d6ea70828f5f10bece61a40" - integrity sha512-ri3cauGfQBjvRvwwJIQfnHlpOfFvQz0iy5hVd82Tr4t0LkqpqFr248tuO8jkI39JXelUcX7TCGNHneeMMPZM1A== +unleash-proxy-client@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.7.2.tgz#c6166bbaf293f8dea12cf65d061d72234c5b76a8" + integrity sha512-1SvHsl3kQh1DT9EKMQsN9alOvXZEz9hpxa3mG6QWtTmXJqa6VZi25dQ2U8Y2KAULKg6ARLMUQkod74Fe/pKp0g== dependencies: tiny-emitter "^2.1.0" uuid "^9.0.1" From 6c75948a3b6819a8b9fc7301b9e4e1981d652237 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 16 Jan 2025 13:00:41 +0000 Subject: [PATCH 10/21] v4.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04a4733..5df2724 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/proxy-client-react", - "version": "4.5.0", + "version": "4.5.1", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", From 2abbff7fde7eb9ab54ab125d488eb3161dae0a04 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Wed, 5 Feb 2025 12:10:31 +0100 Subject: [PATCH 11/21] chore: update js client (#189) --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 5df2724..31f4306 100644 --- a/package.json +++ b/package.json @@ -50,12 +50,12 @@ "react-dom": "^18.2.0", "react-test-renderer": "^18.2.0", "typescript": "^5.3.2", - "unleash-proxy-client": "^3.7.2", + "unleash-proxy-client": "^3.7.3", "vite": "^4.5.0", "vite-plugin-dts": "^3.6.3", "vitest": "^0.34.6" }, "peerDependencies": { - "unleash-proxy-client": "^3.7.2" + "unleash-proxy-client": "^3.7.3" } } diff --git a/yarn.lock b/yarn.lock index 97fb114..d27d8dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1972,10 +1972,10 @@ universalify@^0.2.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== -unleash-proxy-client@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.7.2.tgz#c6166bbaf293f8dea12cf65d061d72234c5b76a8" - integrity sha512-1SvHsl3kQh1DT9EKMQsN9alOvXZEz9hpxa3mG6QWtTmXJqa6VZi25dQ2U8Y2KAULKg6ARLMUQkod74Fe/pKp0g== +unleash-proxy-client@^3.7.3: + version "3.7.3" + resolved "https://registry.yarnpkg.com/unleash-proxy-client/-/unleash-proxy-client-3.7.3.tgz#2f69b4d4a3ea673f6f1c647b50e5a4433b955045" + integrity sha512-Cd7ovrhAIpwveNFdtpzs7HO0WeArC2fbjIVGL2Cjza8bHD+jJ1JbSuy3tFuKvvUkbVKq/EGV0RgosEa/3UVLgg== dependencies: tiny-emitter "^2.1.0" uuid "^9.0.1" From 55db6820f46af99a9d33b2fd991a2eb3ef35a866 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Wed, 5 Feb 2025 11:31:01 +0000 Subject: [PATCH 12/21] v4.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 31f4306..ccbe371 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/proxy-client-react", - "version": "4.5.1", + "version": "4.5.2", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", From cf9d52c302bd093f9141339cefd0d14ce1756cc2 Mon Sep 17 00:00:00 2001 From: Przemek Maszczynski Date: Fri, 7 Feb 2025 10:39:49 +0100 Subject: [PATCH 13/21] docs: fix wrong import in events section of README.md (#191) Listening to events contained import of `useUnleashContext` instead `useUnleashContext` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae41aa8..647c702 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ The core JavaScript client emits various types of events depending on internal a NOTE: `FlagProvider` uses these internal events to provide information through `useFlagsStatus`. ```jsx -import { useUnleashContext, useFlag } from '@unleash/proxy-client-react'; +import { useUnleashClient, useFlag } from '@unleash/proxy-client-react'; const MyComponent = ({ userId }) => { const client = useUnleashClient(); From b783ef4016dbb881ac3d878cffaf5241b047cc35 Mon Sep 17 00:00:00 2001 From: Daniel Basilio Date: Thu, 6 Mar 2025 03:13:15 -0500 Subject: [PATCH 14/21] fix: Throw a better error message if context is null (#192) Now throws an error message if context is not set. --- src/FlagContext.ts | 2 +- src/FlagProvider.test.tsx | 13 +++++-------- src/integration.test.tsx | 10 +++++----- src/useFlag.ts | 6 +++--- src/useFlagContext.test.ts | 17 +++++++++++++++++ src/useFlagContext.ts | 10 ++++++++++ src/useFlags.ts | 6 +++--- src/useFlagsStatus.ts | 6 ++---- src/useUnleashClient.ts | 5 ++--- src/useUnleashContext.ts | 5 ++--- src/useVariant.ts | 6 +++--- 11 files changed, 53 insertions(+), 33 deletions(-) create mode 100644 src/useFlagContext.test.ts create mode 100644 src/useFlagContext.ts diff --git a/src/FlagContext.ts b/src/FlagContext.ts index 537720f..dd0cfc5 100644 --- a/src/FlagContext.ts +++ b/src/FlagContext.ts @@ -17,6 +17,6 @@ export interface IFlagContextValue >; } -const FlagContext = React.createContext(null as never); +const FlagContext = React.createContext(null); export default FlagContext; diff --git a/src/FlagProvider.test.tsx b/src/FlagProvider.test.tsx index 4061eed..8c8d679 100644 --- a/src/FlagProvider.test.tsx +++ b/src/FlagProvider.test.tsx @@ -1,9 +1,9 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { render, screen } from '@testing-library/react'; import { type Mock } from 'vitest'; import { UnleashClient, type IVariant, EVENTS } from 'unleash-proxy-client'; import FlagProvider from './FlagProvider'; -import FlagContext from './FlagContext'; +import { useFlagContext } from './useFlagContext'; import '@testing-library/jest-dom'; const getVariantMock = vi.fn().mockReturnValue('A'); @@ -44,8 +44,7 @@ vi.mock('unleash-proxy-client', async (importOriginal) => { const noop = () => {}; const FlagConsumerAfterClientInit = () => { - const { updateContext, isEnabled, getVariant, client, on } = - useContext(FlagContext); + const { updateContext, isEnabled, getVariant, client, on } = useFlagContext(); const [enabled, setIsEnabled] = useState(false); const [variant, setVariant] = useState(null); const [context, setContext] = useState('nothing'); @@ -71,8 +70,7 @@ const FlagConsumerAfterClientInit = () => { }; const FlagConsumerBeforeClientInit = () => { - const { updateContext, isEnabled, getVariant, client, on } = - useContext(FlagContext); + const { updateContext, isEnabled, getVariant, client, on } = useFlagContext(); const [enabled, setIsEnabled] = useState(false); const [variant, setVariant] = useState(null); const [context, setContext] = useState('nothing'); @@ -162,8 +160,7 @@ test('A memoized consumer should not rerender when the context provider values a const renderCounter = vi.fn(); const MemoizedConsumer = React.memo(() => { - const { updateContext, isEnabled, getVariant, client, on } = - useContext(FlagContext); + const { updateContext, isEnabled, getVariant, client, on } = useFlagContext(); renderCounter(); diff --git a/src/integration.test.tsx b/src/integration.test.tsx index 7205e6e..6ef6652 100644 --- a/src/integration.test.tsx +++ b/src/integration.test.tsx @@ -1,12 +1,12 @@ -import React, { useContext } from 'react'; +import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { EVENTS, UnleashClient } from 'unleash-proxy-client'; import FlagProvider from './FlagProvider'; import useFlagsStatus from './useFlagsStatus'; import { act } from 'react-dom/test-utils'; import useFlag from './useFlag'; +import { useFlagContext } from './useFlagContext'; import useVariant from './useVariant'; -import FlagContext from './FlagContext'; const fetchMock = vi.fn(async () => { return Promise.resolve({ @@ -89,7 +89,7 @@ test('should render toggles', async () => { test('should be ready from the start if bootstrapped', () => { const Component = React.memo(() => { - const { flagsReady } = useContext(FlagContext); + const { flagsReady } = useFlagContext(); return <>{flagsReady ? 'ready' : ''}; }); @@ -183,7 +183,7 @@ test('should render limited times when bootstrapped', async () => { const Component = () => { const enabled = useFlag('test-flag'); - const { flagsReady } = useContext(FlagContext); + const { flagsReady } = useFlagContext(); renders += 1; @@ -229,7 +229,7 @@ test('should resolve values before setting flagsReady', async () => { const Component = () => { const enabled = useFlag('test-flag'); - const { flagsReady } = useContext(FlagContext); + const { flagsReady } = useFlagContext(); renders += 1; diff --git a/src/useFlag.ts b/src/useFlag.ts index 8e13ea8..e853b95 100644 --- a/src/useFlag.ts +++ b/src/useFlag.ts @@ -1,8 +1,8 @@ -import { useContext, useEffect, useState, useRef } from 'react'; -import FlagContext from './FlagContext'; +import { useEffect, useState, useRef } from 'react'; +import { useFlagContext } from './useFlagContext'; const useFlag = (featureName: string) => { - const { isEnabled, client } = useContext(FlagContext); + const { isEnabled, client } = useFlagContext(); const [flag, setFlag] = useState(!!isEnabled(featureName)); const flagRef = useRef(); flagRef.current = flag; diff --git a/src/useFlagContext.test.ts b/src/useFlagContext.test.ts new file mode 100644 index 0000000..8178cf3 --- /dev/null +++ b/src/useFlagContext.test.ts @@ -0,0 +1,17 @@ +import { renderHook } from '@testing-library/react-hooks/native'; +import FlagProvider from "./FlagProvider"; +import { useFlagContext } from "./useFlagContext"; + +test("throws an error if used outside of a FlagProvider", () => { + const { result } = renderHook(() => useFlagContext()); + + expect(result.error).toEqual( + Error("This hook must be used within a FlagProvider") + ); +}); + +test("does not throw an error if used inside of a FlagProvider", () => { + const { result } = renderHook(() => useFlagContext(), { wrapper: FlagProvider }); + + expect(result.error).toBeUndefined(); +}); diff --git a/src/useFlagContext.ts b/src/useFlagContext.ts new file mode 100644 index 0000000..d60c83d --- /dev/null +++ b/src/useFlagContext.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import FlagContext from './FlagContext'; + +export function useFlagContext() { + const context = useContext(FlagContext); + if (!context) { + throw new Error('This hook must be used within a FlagProvider'); + } + return context; +} diff --git a/src/useFlags.ts b/src/useFlags.ts index 1630144..7a10748 100644 --- a/src/useFlags.ts +++ b/src/useFlags.ts @@ -1,8 +1,8 @@ -import { useContext, useEffect, useState } from 'react'; -import FlagContext from './FlagContext'; +import { useEffect, useState } from 'react'; +import { useFlagContext } from './useFlagContext'; const useFlags = () => { - const { client } = useContext(FlagContext); + const { client } = useFlagContext(); const [flags, setFlags] = useState(client.getAllToggles()); useEffect(() => { diff --git a/src/useFlagsStatus.ts b/src/useFlagsStatus.ts index 5e1cda6..1e0c697 100644 --- a/src/useFlagsStatus.ts +++ b/src/useFlagsStatus.ts @@ -1,10 +1,8 @@ /** @format */ - -import { useContext } from 'react'; -import FlagContext from './FlagContext'; +import { useFlagContext } from './useFlagContext'; const useFlagsStatus = () => { - const { flagsReady, flagsError } = useContext(FlagContext); + const { flagsReady, flagsError } = useFlagContext(); return { flagsReady, flagsError }; }; diff --git a/src/useUnleashClient.ts b/src/useUnleashClient.ts index fae34d5..edcd46a 100644 --- a/src/useUnleashClient.ts +++ b/src/useUnleashClient.ts @@ -1,8 +1,7 @@ -import { useContext } from 'react'; -import FlagContext from './FlagContext'; +import { useFlagContext } from './useFlagContext'; const useUnleashClient = () => { - const { client } = useContext(FlagContext); + const { client } = useFlagContext(); return client; }; diff --git a/src/useUnleashContext.ts b/src/useUnleashContext.ts index 477cce7..c1da10f 100644 --- a/src/useUnleashContext.ts +++ b/src/useUnleashContext.ts @@ -1,8 +1,7 @@ -import { useContext } from 'react'; -import FlagContext from './FlagContext'; +import { useFlagContext } from './useFlagContext'; const useUnleashContext = () => { - const { updateContext } = useContext(FlagContext); + const { updateContext } = useFlagContext(); return updateContext; }; diff --git a/src/useVariant.ts b/src/useVariant.ts index f99e18e..90980d1 100644 --- a/src/useVariant.ts +++ b/src/useVariant.ts @@ -1,6 +1,6 @@ -import { useContext, useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { IVariant } from 'unleash-proxy-client'; -import FlagContext from './FlagContext'; +import { useFlagContext } from './useFlagContext'; export const variantHasChanged = ( oldVariant: IVariant, @@ -17,7 +17,7 @@ export const variantHasChanged = ( }; const useVariant = (featureName: string): Partial => { - const { getVariant, client } = useContext(FlagContext); + const { getVariant, client } = useFlagContext(); const [variant, setVariant] = useState(getVariant(featureName)); const variantRef = useRef({ From 7e7d4f6dd9478091d46008f632f03db1a29f9d70 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 6 Mar 2025 08:17:51 +0000 Subject: [PATCH 15/21] v5.0.0-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ccbe371..a89eb22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@unleash/proxy-client-react", - "version": "4.5.2", + "version": "5.0.0-rc.0", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", From 9fe2488c49e9105cd46e44b69cde60ebd0d1593b Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 11 Mar 2025 10:08:39 +0100 Subject: [PATCH 16/21] 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 17/21] 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 18/21] 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 19/21] 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 20/21] 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 21/21] 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 (