diff --git a/README.md b/README.md index 647c702..be53ef4 100644 --- a/README.md +++ b/README.md @@ -348,5 +348,9 @@ Upgrading should be as easy as running yarn again with the new version, but we m #### 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. + ## 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). diff --git a/package.json b/package.json index a89eb22..21e1796 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", "description": "React interface for working with unleash", "type": "module", "main": "./dist/unleash-react.umd.cjs", diff --git a/src/FlagProvider.tsx b/src/FlagProvider.tsx index de05c05..50331f4 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 { type IConfig, IMutableContext, UnleashClient } from 'unleash-proxy-client'; import FlagContext, { type IFlagContextValue } from './FlagContext'; export interface IFlagProvider { @@ -108,11 +108,12 @@ const FlagProvider: FC> = ({ 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: (...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, 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; }