Skip to content

Commit aaa92b9

Browse files
feat(react)!: react server support (#235)
BREAKING CHANGES: - All imports that were previously under the package root (`@spotify-confidence/react`) are now accessible under `@spotify-confidence/react/client`. - Previously, when you did `useConfidence`, the returned instance would be augmented with extra hook like functions such as `useFlag`. These have been removed and you should just use the exported top level hooks. --------- Co-authored-by: Nicklas Lundin <[email protected]>
1 parent d940aa5 commit aaa92b9

File tree

15 files changed

+461
-462
lines changed

15 files changed

+461
-462
lines changed

api/react-client.d.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ConfidenceOptions, Confidence, Context, FlagEvaluation, Value } from '@spotify-confidence/sdk';
2+
import { FC, PropsWithChildren, FunctionComponent, ReactNode } from 'react';
3+
4+
declare const ManagedConfidenceProvider: FC<PropsWithChildren<{
5+
options: ConfidenceOptions;
6+
}>>;
7+
/**
8+
* Confidence Provider for React
9+
* @public
10+
*/
11+
interface ConfidenceProvider extends FunctionComponent<{
12+
confidence: Confidence;
13+
children?: ReactNode;
14+
}> {
15+
WithContext: FC<PropsWithChildren<{
16+
context: Context;
17+
}>>;
18+
}
19+
/**
20+
* Confidence Provider for React
21+
* @public
22+
*/
23+
declare const ConfidenceProvider: ConfidenceProvider;
24+
/**
25+
* Enables using Confidence
26+
* @public
27+
*/
28+
declare const useConfidence: () => Confidence;
29+
/**
30+
* Use with given Confidence Context
31+
* @public
32+
*/
33+
declare function useWithContext(context: Context, parent?: Confidence): Confidence;
34+
/**
35+
* Use Confidence Context
36+
* @public
37+
*/
38+
declare function useConfidenceContext(confidence?: Confidence): Context;
39+
/**
40+
* Use EvaluateFlag
41+
* @public */
42+
declare function useEvaluateFlag(path: string, defaultValue: string, confidence?: Confidence): FlagEvaluation<string>;
43+
declare function useEvaluateFlag(path: string, defaultValue: number, confidence?: Confidence): FlagEvaluation<number>;
44+
declare function useEvaluateFlag(path: string, defaultValue: boolean, confidence?: Confidence): FlagEvaluation<boolean>;
45+
declare function useEvaluateFlag<T extends Value>(path: string, defaultValue: T, confidence?: Confidence): FlagEvaluation<T>;
46+
/**
47+
* Use Flag
48+
* @public
49+
*/
50+
declare function useFlag(path: string, defaultValue: string, confidence?: Confidence): string;
51+
declare function useFlag(path: string, defaultValue: number, confidence?: Confidence): number;
52+
declare function useFlag(path: string, defaultValue: boolean, confidence?: Confidence): boolean;
53+
declare function useFlag<T extends Value>(path: string, defaultValue: T, confidence?: Confidence): T;
54+
55+
export { ConfidenceProvider, ManagedConfidenceProvider, useConfidence, useConfidenceContext, useEvaluateFlag, useFlag, useWithContext };

api/react-server.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Confidence } from '@spotify-confidence/sdk';
2+
import React, { ReactNode } from 'react';
3+
4+
declare function ConfidenceProvider(props: {
5+
confidence: Confidence;
6+
children?: ReactNode;
7+
}): Promise<React.JSX.Element>;
8+
9+
export { ConfidenceProvider };

api/react.d.ts

Lines changed: 0 additions & 120 deletions
This file was deleted.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"@spotify/tsconfig": "^15.0.0",
3434
"@swc/core": "^1.4.13",
3535
"@types/jest": "^29.5.3",
36-
"@types/react": "^18.2.17",
3736
"@typescript-eslint/eslint-plugin": "^5.62.0",
3837
"@typescript-eslint/parser": "^5.62.0",
3938
"@yarnpkg/types": "^4.0.0",

packages/react/README.md

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ yarn add @spotify-confidence/react
1616

1717
### Initializing the ConfidenceProvider
1818

19-
The Confidence React integration has a Provider that needs to be initialized. It accepts a Confidence instance and should wrap your component tree.
19+
The Confidence React integration has a Provider that needs to be initialized. It accepts a Confidence instance and should wrap your component tree. Here's an example for client-side rendering:
2020

2121
```ts
22-
import { Confidence } from '@spotify-confidence/sdk';
22+
import { Confidence } from '@spotify-confidence/sdk/';
23+
import { ConfidenceProvider } from '@spotify-confidence/sdk/react/client';
2324

25+
// Client-side initialization
2426
const confidence = Confidence.create({
2527
clientSecret: 'mysecret',
2628
region: 'eu',
27-
environment: 'client',
29+
environment: 'client', // Note: For client-side rendering
2830
timeout: 1000,
2931
});
3032

@@ -39,6 +41,8 @@ function App() {
3941
}
4042
```
4143

44+
For server-side rendering setup, see the [Server-Side Rendering Support](#server-side-rendering-support) section below.
45+
4246
Anywhere in the sub-tree under the `ConfidenceProvider` you can now access the confidence instance with the `useConfidence()` hook. The hook actually returns an instance of `ConfidenceReact` which is a wrapper around the normal `Confidence` API with some slight adaptations to integrate better with React. You can read more about the differences in the following sections.
4347

4448
### Managing context
@@ -66,6 +70,120 @@ The hooks are also reactive so that if the context changes, any components using
6670

6771
If the hooks can't resolve the flag values withing the timeout specified on the Confidence instance, they will instead return the default value.
6872

73+
### Server-Side Rendering Support (experimental)
74+
75+
The Confidence React SDK now supports server-side rendering (SSR) and React Server Components (RSC) for instance in Next.js. The SDK provides separate entry points for client and server components:
76+
77+
- `@spotify-confidence/react/client` - For client components
78+
- `@spotify-confidence/react/server` - For server components
79+
80+
When using the SDK in a server environment:
81+
82+
1. Create a Confidence instance for server using the React.cache as the scope in CacheOptions.
83+
2. Provide an accessor for the Confidence instance using `withContext`.
84+
3. Use direct flag evaluation in server components.
85+
86+
Here's an example of how to use Confidence in a Next.js application:
87+
88+
```ts
89+
// app/confidence.ts (Server-side configuration)
90+
import { Confidence } from '@spotify-confidence/sdk';
91+
import React from 'react';
92+
93+
const confidence = Confidence.create({
94+
clientSecret: process.env.CONFIDENCE_CLIENT_SECRET!,
95+
environment: 'backend',
96+
timeout: 1000,
97+
logger: console,
98+
cache: {
99+
scope: React.cache, // Use React.cache for server-side caching
100+
},
101+
});
102+
103+
// Confidence accessor for use in RSC.
104+
export const getConfidence = (context: Context): Confidence => {
105+
return confidence.withContext(context);
106+
};
107+
108+
// app/components/ServerComponent.tsx
109+
import { cookies } from 'next/headers';
110+
import { getConfidence } from '../confidence';
111+
112+
export const ServerComponent = async () => {
113+
const cookieStore = await cookies();
114+
const targeting_key = cookieStore.get('cnfdVisitorId')?.value; // or a unique targeting value of your choice
115+
const confidence = getConfidence({ targeting_key });
116+
117+
// Direct flag evaluation in server components
118+
const color = await confidence.getFlag('my-feature-flag.color', 'blue');
119+
120+
return <div style={{ color }}>Server rendered content</div>;
121+
};
122+
```
123+
124+
#### Server and Client
125+
126+
If you also have interactive (client side) components that benefit from feature flagging support.
127+
Using the pattern below in addition to the above server side example will allow you to have the flag evaluations
128+
seamlessly transferred from the server component to the client components.
129+
130+
Please note:
131+
132+
- Server components use direct flag evaluation with `evaluateFlag` or `getFlag`
133+
- Client components use hooks (`useFlag`, `useConfidence`) for interactive features
134+
- Use React.cache for efficient server-side caching
135+
- The SDK automatically handles synchronization between server and client
136+
- Mutating the context in a client side component does not affect the server side confidence instance.
137+
138+
```tsx
139+
// app/layout.tsx
140+
import { ConfidenceProvider } from '@spotify-confidence/react/server';
141+
import { getConfidence } from '../confidence';
142+
import { ClientComponent } from 'components/ClientComponent';
143+
import { ServerComponent } from 'components/ServerComponent';
144+
145+
export default async function Layout() {
146+
const cookieStore = await cookies();
147+
const targeting_key = cookieStore.get('cnfdVisitorId')?.value;
148+
const confidence = getConfidence({ targeting_key });
149+
150+
return (
151+
<div>
152+
<ConfidenceProvider confidence={confidence}>
153+
<Suspense fallback={<h1>Loading...</h1>}>
154+
<ClientComponent>
155+
<ServerComponent>
156+
<ClientComponent />
157+
</ServerComponent>
158+
</ClientComponent>
159+
</Suspense>
160+
</ConfidenceProvider>
161+
</div>
162+
);
163+
}
164+
165+
// app/components/ClientComponent.tsx
166+
('use client');
167+
import { useConfidence, useFlag } from '@spotify-confidence/react/client';
168+
169+
export const ClientComponent = () => {
170+
// Use hooks in client components
171+
const confidence = useConfidence();
172+
const fontSize = useFlag('my-feature-flag.size', 12);
173+
174+
return (
175+
<div>
176+
<div style={{ fontSize }}>Client rendered content</div>
177+
<button onClick={() => confidence.setContext({ locale: 'sv-SE' })}>Choose Swedish</button>
178+
</div>
179+
);
180+
};
181+
```
182+
183+
#### In-depth
184+
185+
Coming soon.
186+
69187
### Tracking events
70188

71189
The event tracking API is available on the Confidence instance as usual. See the [SDK Readme](https://github.com/spotify/confidence-sdk-js/blob/main/packages/sdk/README.md#event-tracking) for details.

packages/react/package.json

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,43 @@
1717
},
1818
"peerDependencies": {
1919
"@spotify-confidence/sdk": ">=0.1.4 <=0.2.2",
20-
"react": "^18.2.0"
20+
"react": "^18 || ^19"
2121
},
2222
"devDependencies": {
2323
"@spotify-confidence/sdk": "workspace:*",
24-
"react": "^18.2.0",
24+
"@types/react": "^18",
25+
"react": "^19",
2526
"rollup": "4.24.0",
2627
"typescript": "5.1.6"
2728
},
2829
"exports": {
29-
".": {
30-
"import": "./build/index.js",
31-
"types": "./build/index.d.ts"
30+
"./client": {
31+
"import": "./build/client.js",
32+
"types": "./build/client.d.ts"
33+
},
34+
"./server": {
35+
"import": "./build/server.js",
36+
"types": "./build/server.d.ts"
3237
}
3338
},
3439
"type": "module",
3540
"publishConfig": {
3641
"registry": "https://registry.npmjs.org/",
3742
"access": "public",
3843
"exports": {
39-
".": {
40-
"import": "./dist/index.mjs",
41-
"require": "./dist/index.cjs",
42-
"types": "./dist/index.d.ts"
44+
"./client": {
45+
"import": "./dist/client.mjs",
46+
"require": "./dist/client.cjs",
47+
"types": "./dist/client.d.ts"
48+
},
49+
"./server": {
50+
"import": "./dist/server.mjs",
51+
"require": "./dist/server.cjs",
52+
"types": "./dist/server.d.ts"
4353
}
4454
}
55+
},
56+
"dependencies": {
57+
"server-only": "^0.0.1"
4558
}
4659
}

0 commit comments

Comments
 (0)