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';