diff --git a/packages/react-sdk/README.md b/packages/react-sdk/README.md index 32a735b5..06c24922 100644 --- a/packages/react-sdk/README.md +++ b/packages/react-sdk/README.md @@ -396,6 +396,27 @@ function FeatureOptIn() { Note: To change the `user.id` or `company.id`, you need to update the props passed to `BucketProvider` instead of using these hooks. +### `useClient()` + +Returns the `BucketClient` used by the `BucketProvider`. The client offers more functionality that +is not directly accessible thorough the other hooks. + +```tsx +import { useClient } from "@bucketco/react-sdk"; + +function LoggingWrapper({ children }: { children: ReactNode }) { + const client = useClient(); + + useEffect(() => { + client.on("enabledCheck", (evt) => { + console.log(`The feature ${evt.key} is ${evt.value} for user.`); + }); + }, [client]); + + return children; +} +``` + ## Content Security Policy (CSP) See [CSP](https://github.com/bucketco/bucket-javascript-sdk/blob/main/packages/browser-sdk/README.md#content-security-policy-csp) for info on using Bucket React SDK with CSP diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index 3a632c28..867a55dc 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@bucketco/react-sdk", - "version": "3.0.0-alpha.6", + "version": "3.0.0-alpha.7", "license": "MIT", "repository": { "type": "git", diff --git a/packages/react-sdk/src/index.tsx b/packages/react-sdk/src/index.tsx index f90b725a..711f82cd 100644 --- a/packages/react-sdk/src/index.tsx +++ b/packages/react-sdk/src/index.tsx @@ -166,7 +166,7 @@ type EmptyConfig = { payload: undefined; }; -type Feature = { +export type Feature = { isEnabled: boolean; isLoading: boolean; config: MaterializedFeatures[TKey] extends boolean @@ -195,12 +195,10 @@ type Feature = { export function useFeature( key: TKey, ): Feature { + const client = useClient(); const { features: { isLoading }, - client, - provider, } = useContext(ProviderContext); - ensureProvider(provider); const track = () => client?.track(key); const requestFeedback = (opts: RequestFeedbackOptions) => @@ -241,8 +239,7 @@ export function useFeature( * ``` */ export function useTrack() { - const { client, provider } = useContext(ProviderContext); - ensureProvider(provider); + const client = useClient(); return (eventName: string, attributes?: Record | null) => client?.track(eventName, attributes); } @@ -262,8 +259,7 @@ export function useTrack() { * ``` */ export function useRequestFeedback() { - const { client, provider } = useContext(ProviderContext); - ensureProvider(provider); + const client = useClient(); return (options: RequestFeedbackData) => client?.requestFeedback(options); } @@ -284,8 +280,7 @@ export function useRequestFeedback() { * ``` */ export function useSendFeedback() { - const { client, provider } = useContext(ProviderContext); - ensureProvider(provider); + const client = useClient(); return (opts: UnassignedFeedback) => client?.feedback(opts); } @@ -303,8 +298,7 @@ export function useSendFeedback() { * ``` */ export function useUpdateUser() { - const { client, provider } = useContext(ProviderContext); - ensureProvider(provider); + const client = useClient(); return (opts: { [key: string]: string | number | undefined }) => client?.updateUser(opts); } @@ -323,8 +317,7 @@ export function useUpdateUser() { * ``` */ export function useUpdateCompany() { - const { client, provider } = useContext(ProviderContext); - ensureProvider(provider); + const client = useClient(); return (opts: { [key: string]: string | number | undefined }) => client?.updateCompany(opts); @@ -345,16 +338,30 @@ export function useUpdateCompany() { * ``` */ export function useUpdateOtherContext() { - const { client, provider } = useContext(ProviderContext); - ensureProvider(provider); + const client = useClient(); return (opts: { [key: string]: string | number | undefined }) => client?.updateOtherContext(opts); } -function ensureProvider(provider: boolean) { +/** + * Returns the current `BucketClient` used by the `BucketProvider`. + * + * This is useful if you need to access the `BucketClient` outside of the `BucketProvider`. + * + * ```ts + * const client = useClient(); + * client.on("configCheck", () => { + * console.log("configCheck hook called"); + * }); + * ``` + */ +export function useClient() { + const { client, provider } = useContext(ProviderContext); if (!provider) { throw new Error( "BucketProvider is missing. Please ensure your component is wrapped with a BucketProvider.", ); } + + return client; } diff --git a/packages/react-sdk/test/usage.test.tsx b/packages/react-sdk/test/usage.test.tsx index 4194bbd0..bc47dce4 100644 --- a/packages/react-sdk/test/usage.test.tsx +++ b/packages/react-sdk/test/usage.test.tsx @@ -19,6 +19,7 @@ import { version } from "../package.json"; import { BucketProps, BucketProvider, + useClient, useFeature, useRequestFeedback, useSendFeedback, @@ -138,6 +139,7 @@ beforeAll(() => }, }), ); + afterEach(() => server.resetHandlers()); afterAll(() => server.close()); @@ -311,7 +313,7 @@ describe("useSendFeedback", () => { await waitFor(async () => { await result.current({ - featureId: "123", + featureKey: "huddles", score: 5, }); expect(events).toStrictEqual(["FEEDBACK"]); @@ -432,3 +434,17 @@ describe("useUpdateOtherContext", () => { unmount(); }); }); + +describe("useClient", () => { + test("gets the client", async () => { + const { result: clientFn, unmount } = renderHook(() => useClient(), { + wrapper: ({ children }) => getProvider({ children }), + }); + + await waitFor(async () => { + expect(clientFn.current).toBeDefined(); + }); + + unmount(); + }); +});