diff --git a/packages/sdk/react-universal/example/app/components/helloClientComponent.tsx b/packages/sdk/react-universal/example/app/components/helloClientComponent.tsx index ebdb4d25b..84bfeccce 100644 --- a/packages/sdk/react-universal/example/app/components/helloClientComponent.tsx +++ b/packages/sdk/react-universal/example/app/components/helloClientComponent.tsx @@ -1,24 +1,21 @@ 'use client'; -import { useLDClient } from '@launchdarkly/react-universal-sdk/client'; +import { useVariationDetail } from '@launchdarkly/react-universal-sdk/client'; export default function HelloClientComponent() { - const ldc = useLDClient(); - - // WARNING: Using the ldClient to evaluate flags directly like this in prod - // can result in high event volumes. This example is contrived and is meant for - // demo purposes only. The recommended way is to utilise the `useVariation` hooks - // which should be supported soon. - const flagValue = ldc.variation('my-boolean-flag-1'); + // You need to set evaluationReasons to true when initializing the LDProvider to useVariationDetail. + // Note: in the future evaluationReasons will be renamed withReasons. + const detail = useVariationDetail('my-boolean-flag-1'); return (

- {flagValue + {detail.value ? 'This flag is evaluating True running Client-Side JavaScript' : 'This flag is evaluating False running Client-Side JavaScript'}

+

Reason: {detail.reason?.kind ?? 'reason is null'}

); diff --git a/packages/sdk/react-universal/example/app/components/helloIdentify.tsx b/packages/sdk/react-universal/example/app/components/helloIdentify.tsx index a5a540ec7..d90b5662c 100644 --- a/packages/sdk/react-universal/example/app/components/helloIdentify.tsx +++ b/packages/sdk/react-universal/example/app/components/helloIdentify.tsx @@ -4,18 +4,14 @@ import { useState } from 'react'; import { useCookies } from 'react-cookie'; import type { JSSdk } from '@launchdarkly/react-universal-sdk'; -import { useLDClient } from '@launchdarkly/react-universal-sdk/client'; +import { useLDClient, useVariation } from '@launchdarkly/react-universal-sdk/client'; export default function HelloIdentify() { - const ldc = useLDClient(); const [_, setCookie] = useCookies(['ld']); const [contextKey, setContextKey] = useState(''); - // WARNING: Using the ldClient to evaluate flags directly like this in prod - // can result in high event volumes. This example is contrived and is meant for - // demo purposes only. The recommended way is to utilise the `useVariation` hooks - // which should be supported soon. - const flagValue = ldc.variation('my-boolean-flag-1'); + const ldc = useLDClient(); + const flagValue = useVariation('my-boolean-flag-1'); function onClickLogin() { const context = { kind: 'user', key: contextKey }; diff --git a/packages/sdk/react-universal/example/app/components/helloServerComponent.tsx b/packages/sdk/react-universal/example/app/components/helloServerComponent.tsx index f84e063ba..b56875d6c 100644 --- a/packages/sdk/react-universal/example/app/components/helloServerComponent.tsx +++ b/packages/sdk/react-universal/example/app/components/helloServerComponent.tsx @@ -1,10 +1,9 @@ import { getLDContext } from '@/app/utils'; -import { useLDClientRsc } from '@launchdarkly/react-universal-sdk/server'; +import { useVariationRsc } from '@launchdarkly/react-universal-sdk/server'; export default async function HelloServerComponent() { - const ldc = await useLDClientRsc(getLDContext()); - const flagValue = ldc.variation('my-boolean-flag-1'); + const flagValue = await useVariationRsc('my-boolean-flag-1', getLDContext()); return (
diff --git a/packages/sdk/react-universal/example/app/layout.tsx b/packages/sdk/react-universal/example/app/layout.tsx index 4d03a7df8..2965b31e2 100644 --- a/packages/sdk/react-universal/example/app/layout.tsx +++ b/packages/sdk/react-universal/example/app/layout.tsx @@ -32,7 +32,11 @@ export default async function RootLayout({ return ( - + {children} diff --git a/packages/sdk/react-universal/package.json b/packages/sdk/react-universal/package.json index f93f2ff28..c60b35f20 100644 --- a/packages/sdk/react-universal/package.json +++ b/packages/sdk/react-universal/package.json @@ -67,6 +67,7 @@ "typescript": "5.1.6" }, "dependencies": { + "@launchdarkly/js-client-sdk-common": "^1.1.4", "@launchdarkly/node-server-sdk": "^9.4.6", "launchdarkly-js-client-sdk": "^3.4.0" }, diff --git a/packages/sdk/react-universal/src/client/hooks/index.ts b/packages/sdk/react-universal/src/client/hooks/index.ts index 918a4e1f7..c9231d2a9 100644 --- a/packages/sdk/react-universal/src/client/hooks/index.ts +++ b/packages/sdk/react-universal/src/client/hooks/index.ts @@ -1,3 +1,3 @@ -// TODO: Implement variation and typed variation hooks. +export * from './variation'; export * from './useLDClient'; diff --git a/packages/sdk/react-universal/src/client/hooks/variation/index.ts b/packages/sdk/react-universal/src/client/hooks/variation/index.ts new file mode 100644 index 000000000..0a013f009 --- /dev/null +++ b/packages/sdk/react-universal/src/client/hooks/variation/index.ts @@ -0,0 +1 @@ +export * from './useVariation'; diff --git a/packages/sdk/react-universal/src/client/hooks/variation/useTypedVariation.ts b/packages/sdk/react-universal/src/client/hooks/variation/useTypedVariation.ts new file mode 100644 index 000000000..34beff06c --- /dev/null +++ b/packages/sdk/react-universal/src/client/hooks/variation/useTypedVariation.ts @@ -0,0 +1,54 @@ +import type { LDEvaluationDetailTyped } from '@launchdarkly/js-client-sdk-common'; + +import { useLDClient } from '../useLDClient'; + +export const useTypedVariation = ( + key: string, + defaultValue: T, +): T => { + const ldClient = useLDClient(); + + switch (typeof defaultValue) { + // case 'boolean': + // return ldClient.boolVariation(key, defaultValue as boolean) as T; + // case 'number': + // return ldClient.numberVariation(key, defaultValue as number) as T; + // case 'string': + // return ldClient.stringVariation(key, defaultValue as string) as T; + // case 'undefined': + // case 'object': + // return ldClient.jsonVariation(key, defaultValue) as T; + default: + return ldClient.variation(key, defaultValue); + } +}; + +export const useTypedVariationDetail = ( + key: string, + defaultValue: T, +): LDEvaluationDetailTyped => { + const ldClient = useLDClient(); + + switch (typeof defaultValue) { + // case 'boolean': + // return ldClient.boolVariationDetail( + // key, + // defaultValue as boolean, + // ) as LDEvaluationDetailTyped; + // case 'number': + // return ldClient.numberVariationDetail( + // key, + // defaultValue as number, + // ) as LDEvaluationDetailTyped; + // case 'string': + // return ldClient.stringVariationDetail( + // key, + // defaultValue as string, + // ) as LDEvaluationDetailTyped; + // case 'undefined': + // case 'object': + // return ldClient.jsonVariationDetail(key, defaultValue) as LDEvaluationDetailTyped; + default: + return ldClient.variationDetail(key, defaultValue) as LDEvaluationDetailTyped; + } +}; diff --git a/packages/sdk/react-universal/src/client/hooks/variation/useVariation.ts b/packages/sdk/react-universal/src/client/hooks/variation/useVariation.ts new file mode 100644 index 000000000..c8d06c1f7 --- /dev/null +++ b/packages/sdk/react-universal/src/client/hooks/variation/useVariation.ts @@ -0,0 +1,14 @@ +import { useTypedVariation, useTypedVariationDetail } from './useTypedVariation'; + +export const useVariation = (key: string, defaultValue?: boolean) => + useTypedVariation(key, defaultValue); + +/** + * Note that this will only work if you have set `withReasons` to true in {@link LDOptions}. + * Otherwise, the `reason` property of the result will be null. + * + * @param key + * @param defaultValue + */ +export const useVariationDetail = (key: string, defaultValue?: boolean) => + useTypedVariationDetail(key, defaultValue); diff --git a/packages/sdk/react-universal/src/ldClientRsc.ts b/packages/sdk/react-universal/src/ldClientRsc.ts index 51398a249..8d74de47e 100644 --- a/packages/sdk/react-universal/src/ldClientRsc.ts +++ b/packages/sdk/react-universal/src/ldClientRsc.ts @@ -1,12 +1,21 @@ -import type { LDContext, LDFlagSet, LDFlagValue } from '@launchdarkly/node-server-sdk'; +import type { + LDContext, + LDEvaluationDetail, + LDFlagSet, + LDFlagValue, +} from '@launchdarkly/node-server-sdk'; import { isServer } from './isServer'; import type { JSSdk } from './types'; +// GOTCHA: Partially implement the js sdk. +// Omit variationDetail because its return type is incompatible with js-core. +type PartialJSSdk = Omit, 'variationDetail'>; + /** * A partial ldClient suitable for RSC and server side rendering. */ -export class LDClientRsc implements Partial { +export class LDClientRsc implements PartialJSSdk { constructor( private readonly ldContext: LDContext, private readonly bootstrap: LDFlagSet, @@ -27,4 +36,14 @@ export class LDClientRsc implements Partial { } return this.bootstrap[key] ?? defaultValue; } + + variationDetail(key: string, defaultValue?: LDFlagValue): LDEvaluationDetail { + if (isServer) { + // On the server during ssr, call variation for analytics purposes. + global.nodeSdk.variationDetail(key, this.ldContext, defaultValue).then(/* ignore */); + } + + const { reason, variation: variationIndex } = this.bootstrap.$flagsState[key]; + return { value: this.bootstrap[key], reason, variationIndex }; + } } diff --git a/packages/sdk/react-universal/src/server/getBootstrap.ts b/packages/sdk/react-universal/src/server/getBootstrap.ts index 5c985120a..e5bcb9fb2 100644 --- a/packages/sdk/react-universal/src/server/getBootstrap.ts +++ b/packages/sdk/react-universal/src/server/getBootstrap.ts @@ -8,6 +8,6 @@ import type { LDContext } from '@launchdarkly/node-server-sdk'; * @returns A promise which resolves to a json object suitable for bootstrapping the js sdk. */ export const getBootstrap = async (context: LDContext) => { - const allFlags = await global.nodeSdk.allFlagsState(context); + const allFlags = await global.nodeSdk.allFlagsState(context, { withReasons: true }); return allFlags?.toJSON(); }; diff --git a/packages/sdk/react-universal/src/server/hooks/index.ts b/packages/sdk/react-universal/src/server/hooks/index.ts new file mode 100644 index 000000000..b842114e6 --- /dev/null +++ b/packages/sdk/react-universal/src/server/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './variation'; + +export * from './useLDClientRsc'; diff --git a/packages/sdk/react-universal/src/server/useLDClientRsc.ts b/packages/sdk/react-universal/src/server/hooks/useLDClientRsc.ts similarity index 91% rename from packages/sdk/react-universal/src/server/useLDClientRsc.ts rename to packages/sdk/react-universal/src/server/hooks/useLDClientRsc.ts index dec957611..2d698aef7 100644 --- a/packages/sdk/react-universal/src/server/useLDClientRsc.ts +++ b/packages/sdk/react-universal/src/server/hooks/useLDClientRsc.ts @@ -2,8 +2,8 @@ import { cache } from 'react'; import type { LDContext } from '@launchdarkly/node-server-sdk'; -import { LDClientRsc } from '../ldClientRsc'; -import { getBootstrap } from './getBootstrap'; +import { LDClientRsc } from '../../ldClientRsc'; +import { getBootstrap } from '../getBootstrap'; const ldClientRsc = 'ldClientRsc'; const getServerCache = cache(() => new Map()); diff --git a/packages/sdk/react-universal/src/server/hooks/variation/index.ts b/packages/sdk/react-universal/src/server/hooks/variation/index.ts new file mode 100644 index 000000000..08db69773 --- /dev/null +++ b/packages/sdk/react-universal/src/server/hooks/variation/index.ts @@ -0,0 +1 @@ +export * from './useVariationRsc'; diff --git a/packages/sdk/react-universal/src/server/hooks/variation/useVariationRsc.ts b/packages/sdk/react-universal/src/server/hooks/variation/useVariationRsc.ts new file mode 100644 index 000000000..b8aacb94b --- /dev/null +++ b/packages/sdk/react-universal/src/server/hooks/variation/useVariationRsc.ts @@ -0,0 +1,13 @@ +import type { LDContext, LDFlagValue } from '@launchdarkly/node-server-sdk'; + +import { useLDClientRsc } from '../useLDClientRsc'; + +export const useVariationRsc = async (key: string, context: LDContext, def?: LDFlagValue) => { + const ldc = await useLDClientRsc(context); + return ldc.variation(key, def); +}; + +export const useVariationDetailRsc = async (key: string, context: LDContext, def?: LDFlagValue) => { + const ldc = await useLDClientRsc(context); + return ldc.variationDetail(key, def); +}; diff --git a/packages/sdk/react-universal/src/server/index.ts b/packages/sdk/react-universal/src/server/index.ts index 5d75eba27..20736e68f 100644 --- a/packages/sdk/react-universal/src/server/index.ts +++ b/packages/sdk/react-universal/src/server/index.ts @@ -1,3 +1,3 @@ export * from './initNodeSdk'; -export * from './useLDClientRsc'; +export * from './hooks'; export * from './getBootstrap';