flagmint-react-sdk 0.7.12 → 0.7.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -362
- package/dist/client.cjs +12 -3
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +148 -0
- package/dist/client.d.ts +148 -0
- package/dist/client.js +12 -3
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +12 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,60 +4,54 @@ A React wrapper for [flagmint-js-sdk](https://www.npmjs.com/package/flagmint-js-
|
|
|
4
4
|
|
|
5
5
|
## ✨ Features
|
|
6
6
|
|
|
7
|
-
- 🎯 **Fine-grained reactivity** - Only components using specific
|
|
7
|
+
- 🎯 **Fine-grained reactivity** - Only components using a specific flag re-render when that flag changes
|
|
8
8
|
- 🚀 **SSR-safe** - No global state leakage between server requests
|
|
9
9
|
- 📦 **TypeScript support** - Full type safety with generics for flag values and context
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
10
|
+
- ⚡ **Real-time updates** - WebSocket transport keeps flags in sync without polling
|
|
11
|
+
- 🔄 **Offline cache** - Cached flags served instantly on load, even before the server responds
|
|
12
|
+
- 🔁 **Auto fallback** - Falls back to long-polling if WebSocket is unavailable
|
|
13
|
+
- 🔐 **Deferred initialization** - Safe to use in authenticated routes without blocking public pages
|
|
14
14
|
|
|
15
15
|
## 📦 Installation
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install react-
|
|
18
|
+
npm install flagmint-react-sdk flagmint-js-sdk
|
|
19
19
|
# or
|
|
20
|
-
|
|
20
|
+
yarn add flagmint-react-sdk flagmint-js-sdk
|
|
21
21
|
# or
|
|
22
|
-
|
|
22
|
+
pnpm add flagmint-react-sdk flagmint-js-sdk
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## 🚀 Quick Start
|
|
26
26
|
|
|
27
|
-
### 1.
|
|
27
|
+
### 1. Add the Provider to Your Root Layout
|
|
28
|
+
|
|
29
|
+
The provider must live as high as possible in your component tree - ideally in your root layout. This ensures the WebSocket connection is created once and persists across route changes.
|
|
28
30
|
|
|
29
31
|
```tsx
|
|
30
|
-
// app/providers.tsx
|
|
31
|
-
'use client'
|
|
32
|
-
import { FlagmintProvider } from 'react-
|
|
32
|
+
// app/providers.tsx
|
|
33
|
+
'use client';
|
|
34
|
+
import { FlagmintProvider } from 'flagmint-react-sdk/client';
|
|
33
35
|
|
|
34
|
-
export
|
|
36
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
35
37
|
return (
|
|
36
|
-
<FlagmintProvider
|
|
38
|
+
<FlagmintProvider
|
|
37
39
|
options={{
|
|
38
40
|
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
39
41
|
transportMode: 'websocket',
|
|
40
|
-
context: {
|
|
41
|
-
userId: 'user-123',
|
|
42
|
-
plan: 'premium'
|
|
43
|
-
}
|
|
44
42
|
}}
|
|
45
43
|
>
|
|
46
44
|
{children}
|
|
47
45
|
</FlagmintProvider>
|
|
48
|
-
)
|
|
46
|
+
);
|
|
49
47
|
}
|
|
50
48
|
```
|
|
51
49
|
|
|
52
50
|
```tsx
|
|
53
51
|
// app/layout.tsx
|
|
54
|
-
import Providers from './providers'
|
|
52
|
+
import { Providers } from './providers';
|
|
55
53
|
|
|
56
|
-
export default function RootLayout({
|
|
57
|
-
children,
|
|
58
|
-
}: {
|
|
59
|
-
children: React.ReactNode
|
|
60
|
-
}) {
|
|
54
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
61
55
|
return (
|
|
62
56
|
<html>
|
|
63
57
|
<body>
|
|
@@ -66,43 +60,104 @@ export default function RootLayout({
|
|
|
66
60
|
</Providers>
|
|
67
61
|
</body>
|
|
68
62
|
</html>
|
|
69
|
-
)
|
|
63
|
+
);
|
|
70
64
|
}
|
|
71
65
|
```
|
|
72
66
|
|
|
67
|
+
> ⚠️ **Important:** Do not place `FlagmintProvider` inside a nested layout or page component. Doing so causes a new WebSocket connection to be created on every route navigation.
|
|
68
|
+
|
|
73
69
|
### 2. Using Flags in Components
|
|
74
70
|
|
|
75
71
|
```tsx
|
|
76
72
|
// components/FeatureComponent.tsx
|
|
77
|
-
'use client'
|
|
78
|
-
import { useFlag,
|
|
73
|
+
'use client';
|
|
74
|
+
import { useFlag, useFlagmintReady } from 'flagmint-react-sdk/client';
|
|
79
75
|
|
|
80
76
|
export default function FeatureComponent() {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
77
|
+
const isReady = useFlagmintReady();
|
|
78
|
+
const showNewDashboard = useFlag<boolean>('new_dashboard', false);
|
|
79
|
+
const resultsPerPage = useFlag<number>('results_per_page', 10);
|
|
80
|
+
const colorScheme = useFlag<string>('color_scheme', 'light');
|
|
81
|
+
|
|
85
82
|
if (!isReady) {
|
|
86
|
-
return <div>Loading
|
|
83
|
+
return <div>Loading...</div>;
|
|
87
84
|
}
|
|
88
|
-
|
|
85
|
+
|
|
89
86
|
return (
|
|
90
|
-
<div>
|
|
91
|
-
{
|
|
92
|
-
<
|
|
87
|
+
<div className={colorScheme}>
|
|
88
|
+
{showNewDashboard && <NewDashboard />}
|
|
89
|
+
<ResultsList limit={resultsPerPage} />
|
|
93
90
|
</div>
|
|
94
|
-
)
|
|
91
|
+
);
|
|
95
92
|
}
|
|
96
93
|
```
|
|
97
94
|
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🔐 Authentication Flows
|
|
98
|
+
|
|
99
|
+
In most apps, flags need user context (user ID, plan, org) that isn't available until after login. The recommended approach is to **always render children** and skip the SDK until the user is authenticated:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// app/FlagmintProviderSDK.tsx
|
|
103
|
+
'use client';
|
|
104
|
+
import { FlagmintProvider } from 'flagmint-react-sdk/client';
|
|
105
|
+
import { useUser } from '@/hooks/useUser';
|
|
106
|
+
import { useBillingData } from '@/hooks/useBillingData';
|
|
107
|
+
|
|
108
|
+
export default function FlagmintProviderSDK({ children }: { children: React.ReactNode }) {
|
|
109
|
+
const profile = useUser().profile;
|
|
110
|
+
const { data: billingData, isLoading } = useBillingData(profile?.org_id);
|
|
111
|
+
|
|
112
|
+
const apiKey = process.env.NEXT_PUBLIC_FLAGMINT_API_KEY;
|
|
113
|
+
|
|
114
|
+
// No API key - skip SDK entirely
|
|
115
|
+
if (!apiKey) return <>{children}</>;
|
|
116
|
+
|
|
117
|
+
// Not logged in or still loading - render children without flags
|
|
118
|
+
// Public routes (/login, /signup) work fine without flags
|
|
119
|
+
if (!profile || isLoading) return <>{children}</>;
|
|
120
|
+
|
|
121
|
+
const context = {
|
|
122
|
+
kind: 'multi',
|
|
123
|
+
user: { kind: 'user', key: profile.id, email: profile.email },
|
|
124
|
+
organization: {
|
|
125
|
+
kind: 'organization',
|
|
126
|
+
key: profile.org_id,
|
|
127
|
+
plan: billingData?.currentPlan?.name,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<FlagmintProvider
|
|
133
|
+
options={{
|
|
134
|
+
apiKey,
|
|
135
|
+
transportMode: 'auto',
|
|
136
|
+
context,
|
|
137
|
+
onError: (error) => console.error('Flagmint error:', error),
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</FlagmintProvider>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This pattern means:
|
|
147
|
+
- `/login`, `/signup` - children render immediately, no spinner
|
|
148
|
+
- Authenticated pages - provider mounts once with full user context
|
|
149
|
+
- Route navigation - same WebSocket connection, no reconnects
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
98
153
|
## 📚 API Reference
|
|
99
154
|
|
|
100
|
-
### FlagmintProvider
|
|
155
|
+
### `FlagmintProvider`
|
|
101
156
|
|
|
102
|
-
|
|
157
|
+
Initializes the `FlagClient` and provides flag context to all children.
|
|
103
158
|
|
|
104
159
|
```tsx
|
|
105
|
-
<FlagmintProvider
|
|
160
|
+
<FlagmintProvider
|
|
106
161
|
options={FlagClientOptions}
|
|
107
162
|
initialFlags={{}}
|
|
108
163
|
deferInitialization={false}
|
|
@@ -116,357 +171,249 @@ The provider component that initializes the FlagClient and provides flag context
|
|
|
116
171
|
| Prop | Type | Default | Description |
|
|
117
172
|
|------|------|---------|-------------|
|
|
118
173
|
| `options` | `FlagClientOptions` | Required | Configuration for the FlagClient |
|
|
119
|
-
| `initialFlags` | `Flags` | `{}` | Initial flag values for SSR
|
|
120
|
-
| `deferInitialization` | `boolean` | `false` | If true,
|
|
174
|
+
| `initialFlags` | `Flags` | `{}` | Initial flag values for SSR hydration |
|
|
175
|
+
| `deferInitialization` | `boolean` | `false` | If true, skip auto-initialization on mount |
|
|
121
176
|
|
|
122
|
-
#### FlagClientOptions
|
|
177
|
+
#### `FlagClientOptions`
|
|
123
178
|
|
|
124
|
-
```
|
|
179
|
+
```typescript
|
|
125
180
|
interface FlagClientOptions<C extends Record<string, any> = Record<string, any>> {
|
|
126
|
-
apiKey: string
|
|
127
|
-
context?: C
|
|
128
|
-
transportMode?: 'auto' | 'websocket' | 'long-polling'
|
|
129
|
-
enableOfflineCache?: boolean
|
|
130
|
-
persistContext?: boolean
|
|
131
|
-
onError?: (error: Error) => void
|
|
132
|
-
previewMode?: boolean
|
|
133
|
-
rawFlags?: Record<string, FlagValue
|
|
134
|
-
deferInitialization?: boolean
|
|
181
|
+
apiKey: string; // Your Flagmint API key
|
|
182
|
+
context?: C; // Evaluation context (user, org, etc.)
|
|
183
|
+
transportMode?: 'auto' | 'websocket' | 'long-polling'; // Default: 'auto'
|
|
184
|
+
enableOfflineCache?: boolean; // Cache flags in localStorage. Default: true
|
|
185
|
+
persistContext?: boolean; // Persist context across sessions. Default: false
|
|
186
|
+
onError?: (error: Error) => void; // Error handler
|
|
187
|
+
previewMode?: boolean; // Evaluate flags locally from rawFlags
|
|
188
|
+
rawFlags?: Record<string, FlagValue>; // Used with previewMode
|
|
189
|
+
deferInitialization?: boolean; // Skip initialization until ready() is called
|
|
135
190
|
}
|
|
136
191
|
```
|
|
137
192
|
|
|
193
|
+
**Transport modes:**
|
|
194
|
+
- `'websocket'` - Real-time updates via WebSocket. Best for production.
|
|
195
|
+
- `'long-polling'` - Periodic HTTP polling. Use for environments that don't support WebSocket.
|
|
196
|
+
- `'auto'` - Tries WebSocket first, falls back to long-polling automatically.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
138
200
|
### Hooks
|
|
139
201
|
|
|
140
|
-
#### useFlag(key, fallback?)
|
|
202
|
+
#### `useFlag(key, fallback?)`
|
|
141
203
|
|
|
142
|
-
|
|
204
|
+
The primary hook for reading a single feature flag. **Only this component re-renders** when this specific flag changes - not when other flags update.
|
|
205
|
+
|
|
206
|
+
Returns `fallback` silently if called outside a `FlagmintProvider`, making it safe to use on public pages before the user is authenticated.
|
|
143
207
|
|
|
144
208
|
```tsx
|
|
145
|
-
const showFeature = useFlag('
|
|
146
|
-
const
|
|
147
|
-
const
|
|
209
|
+
const showFeature = useFlag<boolean>('show_documentation_feature', false);
|
|
210
|
+
const plan = useFlag<string>('color_scheme', 'light');
|
|
211
|
+
const limit = useFlag<number>('results_per_page', 10);
|
|
212
|
+
const config = useFlag<{ logging: boolean }>('advanced_settings', { logging: false });
|
|
148
213
|
```
|
|
149
214
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
215
|
+
| Parameter | Type | Description |
|
|
216
|
+
|-----------|------|-------------|
|
|
217
|
+
| `key` | `string` | The feature flag key |
|
|
218
|
+
| `fallback` | `T` | Value returned when flag is missing or provider not ready |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
#### `useFlags()`
|
|
153
223
|
|
|
154
|
-
|
|
224
|
+
Returns all evaluated flags as a reactive object. Re-renders when **any** flag changes.
|
|
155
225
|
|
|
156
|
-
|
|
226
|
+
Prefer `useFlag` for single flags - `useFlags` is best when you need to pass many flags to a child component at once.
|
|
157
227
|
|
|
158
|
-
|
|
228
|
+
Returns `{}` silently if called outside a `FlagmintProvider`.
|
|
159
229
|
|
|
160
230
|
```tsx
|
|
161
|
-
const
|
|
162
|
-
|
|
231
|
+
const flags = useFlags();
|
|
232
|
+
// { new_dashboard: true, color_scheme: 'dark', results_per_page: 30 }
|
|
163
233
|
```
|
|
164
234
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
#### useFlagmint()
|
|
235
|
+
---
|
|
168
236
|
|
|
169
|
-
|
|
237
|
+
#### `useFlagmint()`
|
|
170
238
|
|
|
171
|
-
|
|
172
|
-
const { client, isReady, isInitialized, updateContext } = useFlagmint()
|
|
239
|
+
Provides access to the underlying `FlagClient` and the ability to update the evaluation context. Use this when the user's context changes after initial load (plan upgrade, org switch, login).
|
|
173
240
|
|
|
174
|
-
|
|
175
|
-
await updateContext({ userId: 'new-user', plan: 'enterprise' })
|
|
176
|
-
```
|
|
241
|
+
Throws if called outside a `FlagmintProvider`.
|
|
177
242
|
|
|
178
|
-
**Returns:**
|
|
179
243
|
```tsx
|
|
180
|
-
{
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
244
|
+
const { client, isInitialized, updateContext } = useFlagmint();
|
|
245
|
+
|
|
246
|
+
// Update context after user upgrades plan
|
|
247
|
+
await updateContext({
|
|
248
|
+
userId: user.id,
|
|
249
|
+
plan: 'enterprise',
|
|
250
|
+
orgId: user.orgId,
|
|
251
|
+
});
|
|
186
252
|
```
|
|
187
253
|
|
|
188
|
-
|
|
254
|
+
| Return | Type | Description |
|
|
255
|
+
|--------|------|-------------|
|
|
256
|
+
| `client` | `FlagClient \| null` | The underlying SDK client instance |
|
|
257
|
+
| `isInitialized` | `boolean` | Whether the client has finished initializing |
|
|
258
|
+
| `updateContext` | `(context) => Promise<void>` | Update evaluation context and re-fetch flags |
|
|
189
259
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```tsx
|
|
193
|
-
const isReady = useFlagmintReady()
|
|
260
|
+
---
|
|
194
261
|
|
|
195
|
-
|
|
196
|
-
return <LoadingSpinner />
|
|
197
|
-
}
|
|
198
|
-
```
|
|
262
|
+
#### `useFlagmintReady()`
|
|
199
263
|
|
|
200
|
-
|
|
264
|
+
Returns `true` when the client has finished initializing and flags have been fetched from the server. Use this to prevent a flash of incorrect content on first load.
|
|
201
265
|
|
|
202
|
-
|
|
266
|
+
Returns `false` silently if called outside a `FlagmintProvider`.
|
|
203
267
|
|
|
204
268
|
```tsx
|
|
205
|
-
const
|
|
206
|
-
|
|
269
|
+
const isReady = useFlagmintReady();
|
|
270
|
+
|
|
271
|
+
if (!isReady) return <Skeleton />;
|
|
272
|
+
|
|
273
|
+
return showNewPricing ? <NewPricing /> : <OldPricing />;
|
|
207
274
|
```
|
|
208
275
|
|
|
209
|
-
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
#### `useFlagmintStore()` *(Advanced)*
|
|
210
279
|
|
|
211
|
-
|
|
280
|
+
Low-level hook that returns the raw Zustand store. **Use this only when building custom hooks on top of the SDK.** For normal flag consumption always use `useFlag`.
|
|
212
281
|
|
|
213
|
-
|
|
282
|
+
Throws immediately if called outside a `FlagmintProvider` - this is intentional, as it helps catch incorrect usage during development.
|
|
214
283
|
|
|
215
284
|
```tsx
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
userId: getCurrentUser().id,
|
|
223
|
-
plan: getCurrentUser().plan
|
|
224
|
-
}
|
|
225
|
-
}}
|
|
226
|
-
>
|
|
227
|
-
<Dashboard />
|
|
228
|
-
</FlagmintProvider>
|
|
229
|
-
)
|
|
285
|
+
// Building a custom hook that combines multiple flags
|
|
286
|
+
function useCanAccessBeta() {
|
|
287
|
+
const store = useFlagmintStore();
|
|
288
|
+
return useStore(store, (state) =>
|
|
289
|
+
state.flags['beta_access'] && state.flags['new_ui']
|
|
290
|
+
);
|
|
230
291
|
}
|
|
231
292
|
```
|
|
232
293
|
|
|
233
|
-
|
|
294
|
+
---
|
|
234
295
|
|
|
235
|
-
|
|
296
|
+
### Hook Reference Summary
|
|
236
297
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
deferInitialization={true}
|
|
245
|
-
>
|
|
246
|
-
<App />
|
|
247
|
-
</FlagmintProvider>
|
|
298
|
+
| Hook | Use When | Outside Provider |
|
|
299
|
+
|------|----------|-----------------|
|
|
300
|
+
| `useFlag(key, fallback)` | Reading a single flag in any component | Returns `fallback` silently |
|
|
301
|
+
| `useFlags()` | Reading multiple flags at once | Returns `{}` silently |
|
|
302
|
+
| `useFlagmint()` | Updating context after login/upgrade | Throws |
|
|
303
|
+
| `useFlagmintReady()` | Gating renders until flags are loaded | Returns `false` silently |
|
|
304
|
+
| `useFlagmintStore()` | Building custom hooks on top of the SDK | Throws |
|
|
248
305
|
|
|
249
|
-
|
|
250
|
-
function LoginPage() {
|
|
251
|
-
const { updateContext } = useFlagmint()
|
|
252
|
-
|
|
253
|
-
const handleLogin = async (user) => {
|
|
254
|
-
// First update context with user info
|
|
255
|
-
await updateContext({
|
|
256
|
-
userId: user.id,
|
|
257
|
-
plan: user.plan,
|
|
258
|
-
locale: user.locale
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
// Flags are now available with user context
|
|
262
|
-
navigate('/dashboard')
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return <LoginForm onLogin={handleLogin} />
|
|
266
|
-
}
|
|
267
|
-
```
|
|
306
|
+
---
|
|
268
307
|
|
|
269
|
-
|
|
308
|
+
## 🔧 Usage Patterns
|
|
270
309
|
|
|
271
|
-
|
|
310
|
+
### TypeScript: Typed Flags
|
|
272
311
|
|
|
273
312
|
```tsx
|
|
274
313
|
// types/flags.ts
|
|
275
314
|
export interface AppFlags {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
darkMode: boolean
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export interface UserContext {
|
|
285
|
-
userId: string
|
|
286
|
-
plan: string
|
|
287
|
-
locale: string
|
|
315
|
+
show_documentation_feature: boolean;
|
|
316
|
+
color_scheme: 'light' | 'dark';
|
|
317
|
+
results_per_page: number;
|
|
318
|
+
advanced_settings: { logging: boolean };
|
|
288
319
|
}
|
|
289
320
|
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
export default function TypedComponent() {
|
|
295
|
-
// TypeScript knows this returns boolean
|
|
296
|
-
const showBeta = useFlag<AppFlags['show-beta-feature']>('show-beta-feature', false)
|
|
297
|
-
|
|
298
|
-
// TypeScript knows this returns the union type
|
|
299
|
-
const plan = useFlag<AppFlags['user-plan']>('user-plan', 'free')
|
|
300
|
-
|
|
301
|
-
return (
|
|
302
|
-
<div>
|
|
303
|
-
{showBeta && <BetaFeature />}
|
|
304
|
-
<div>Plan: {plan}</div>
|
|
305
|
-
</div>
|
|
306
|
-
)
|
|
307
|
-
}
|
|
321
|
+
// In your component
|
|
322
|
+
const colorScheme = useFlag<AppFlags['color_scheme']>('color_scheme', 'light');
|
|
323
|
+
const resultsPerPage = useFlag<AppFlags['results_per_page']>('results_per_page', 10);
|
|
308
324
|
```
|
|
309
325
|
|
|
310
|
-
###
|
|
311
|
-
|
|
312
|
-
#### Next.js App Router
|
|
326
|
+
### Updating Context After Login
|
|
313
327
|
|
|
314
328
|
```tsx
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
{children}
|
|
329
|
-
</FlagmintProvider>
|
|
330
|
-
)
|
|
331
|
-
}
|
|
329
|
+
function Dashboard() {
|
|
330
|
+
const { updateContext, isInitialized } = useFlagmint();
|
|
331
|
+
const user = useCurrentUser();
|
|
332
|
+
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
if (user) {
|
|
335
|
+
updateContext({
|
|
336
|
+
userId: user.id,
|
|
337
|
+
plan: user.subscription.plan,
|
|
338
|
+
orgId: user.orgId,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}, [user?.id]);
|
|
332
342
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
import Providers from './providers'
|
|
336
|
-
import ClientComponent from './ClientComponent'
|
|
337
|
-
|
|
338
|
-
export default async function Page() {
|
|
339
|
-
// Optionally preload flags on server
|
|
340
|
-
let initialFlags = {}
|
|
341
|
-
|
|
342
|
-
try {
|
|
343
|
-
const serverClient = new FlagClient({
|
|
344
|
-
apiKey: process.env.FLAGMINT_API_KEY!,
|
|
345
|
-
context: { server: true }
|
|
346
|
-
})
|
|
347
|
-
await serverClient.ready()
|
|
348
|
-
initialFlags = await serverClient.getFlags() // If this method exists
|
|
349
|
-
} catch (error) {
|
|
350
|
-
console.warn('Failed to load initial flags:', error)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return (
|
|
354
|
-
<Providers initialFlags={initialFlags}>
|
|
355
|
-
<ClientComponent />
|
|
356
|
-
</Providers>
|
|
357
|
-
)
|
|
343
|
+
if (!isInitialized) return <Spinner />;
|
|
344
|
+
return <DashboardContent />;
|
|
358
345
|
}
|
|
359
346
|
```
|
|
360
347
|
|
|
361
|
-
###
|
|
362
|
-
|
|
363
|
-
Update user context dynamically:
|
|
348
|
+
### Preventing Flash of Wrong Content
|
|
364
349
|
|
|
365
350
|
```tsx
|
|
366
|
-
function
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const handlePlanUpgrade = async (newPlan: string) => {
|
|
376
|
-
await updateContext({ plan: newPlan })
|
|
377
|
-
// Component will re-render with new plan-based flags
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return (
|
|
381
|
-
<div>
|
|
382
|
-
<ThemeSelector onChange={handleThemeChange} />
|
|
383
|
-
<PlanUpgrade onUpgrade={handlePlanUpgrade} />
|
|
384
|
-
</div>
|
|
385
|
-
)
|
|
351
|
+
function PricingPage() {
|
|
352
|
+
const isReady = useFlagmintReady();
|
|
353
|
+
const showNewPricing = useFlag<boolean>('new_pricing_page', false);
|
|
354
|
+
|
|
355
|
+
// Without isReady check, user briefly sees old pricing before flags load
|
|
356
|
+
if (!isReady) return <PricingSkeleton />;
|
|
357
|
+
return showNewPricing ? <NewPricing /> : <OldPricing />;
|
|
386
358
|
}
|
|
387
359
|
```
|
|
388
360
|
|
|
389
|
-
###
|
|
361
|
+
### Preview Mode (Local Evaluation)
|
|
390
362
|
|
|
391
|
-
|
|
363
|
+
Use preview mode in Storybook, tests, or demos to evaluate flags locally without a server connection:
|
|
392
364
|
|
|
393
365
|
```tsx
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
return (
|
|
408
|
-
<div>
|
|
409
|
-
<h1>Features</h1>
|
|
410
|
-
{showPremiumFeature ? (
|
|
411
|
-
<PremiumFeature />
|
|
412
|
-
) : (
|
|
413
|
-
<UpgradeBanner />
|
|
414
|
-
)}
|
|
415
|
-
</div>
|
|
416
|
-
)
|
|
417
|
-
}
|
|
366
|
+
<FlagmintProvider
|
|
367
|
+
options={{
|
|
368
|
+
apiKey: 'preview',
|
|
369
|
+
previewMode: true,
|
|
370
|
+
rawFlags: {
|
|
371
|
+
new_dashboard: { value: true },
|
|
372
|
+
color_scheme: { value: 'dark' },
|
|
373
|
+
results_per_page: { value: 30 },
|
|
374
|
+
},
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
<App />
|
|
378
|
+
</FlagmintProvider>
|
|
418
379
|
```
|
|
419
380
|
|
|
420
|
-
###
|
|
381
|
+
### Error Handling
|
|
421
382
|
|
|
422
383
|
```tsx
|
|
423
|
-
<FlagmintProvider
|
|
384
|
+
<FlagmintProvider
|
|
424
385
|
options={{
|
|
425
|
-
apiKey:
|
|
386
|
+
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
426
387
|
onError: (error) => {
|
|
427
|
-
console.error('Flagmint error:', error)
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
388
|
+
console.error('Flagmint error:', error);
|
|
389
|
+
errorReporting.captureException(error);
|
|
390
|
+
},
|
|
431
391
|
}}
|
|
432
392
|
>
|
|
433
393
|
<App />
|
|
434
394
|
</FlagmintProvider>
|
|
435
395
|
```
|
|
436
396
|
|
|
397
|
+
---
|
|
398
|
+
|
|
437
399
|
## 🏗️ Framework Examples
|
|
438
400
|
|
|
439
|
-
### Next.js
|
|
401
|
+
### Next.js App Router
|
|
440
402
|
|
|
441
403
|
```tsx
|
|
442
404
|
// app/layout.tsx
|
|
443
|
-
import
|
|
405
|
+
import { FlagmintProviderSDK } from './FlagmintProviderSDK';
|
|
444
406
|
|
|
445
|
-
export default function RootLayout({ children }) {
|
|
407
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
446
408
|
return (
|
|
447
409
|
<html lang="en">
|
|
448
410
|
<body>
|
|
449
|
-
<
|
|
411
|
+
<FlagmintProviderSDK>
|
|
412
|
+
{children}
|
|
413
|
+
</FlagmintProviderSDK>
|
|
450
414
|
</body>
|
|
451
415
|
</html>
|
|
452
|
-
)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// app/providers.tsx
|
|
456
|
-
'use client'
|
|
457
|
-
import { FlagmintProvider } from 'react-flagmint/client'
|
|
458
|
-
|
|
459
|
-
export default function Providers({ children }) {
|
|
460
|
-
return (
|
|
461
|
-
<FlagmintProvider
|
|
462
|
-
options={{
|
|
463
|
-
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
464
|
-
transportMode: 'long-polling'
|
|
465
|
-
}}
|
|
466
|
-
>
|
|
467
|
-
{children}
|
|
468
|
-
</FlagmintProvider>
|
|
469
|
-
)
|
|
416
|
+
);
|
|
470
417
|
}
|
|
471
418
|
```
|
|
472
419
|
|
|
@@ -474,30 +421,30 @@ export default function Providers({ children }) {
|
|
|
474
421
|
|
|
475
422
|
```tsx
|
|
476
423
|
// src/main.tsx
|
|
477
|
-
import React from 'react'
|
|
478
|
-
import ReactDOM from 'react-dom/client'
|
|
479
|
-
import { FlagmintProvider } from 'react-
|
|
480
|
-
import App from './App'
|
|
424
|
+
import React from 'react';
|
|
425
|
+
import ReactDOM from 'react-dom/client';
|
|
426
|
+
import { FlagmintProvider } from 'flagmint-react-sdk/client';
|
|
427
|
+
import App from './App';
|
|
481
428
|
|
|
482
429
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
483
430
|
<React.StrictMode>
|
|
484
|
-
<FlagmintProvider
|
|
431
|
+
<FlagmintProvider
|
|
485
432
|
options={{
|
|
486
433
|
apiKey: import.meta.env.VITE_FLAGMINT_API_KEY,
|
|
487
|
-
transportMode: 'websocket'
|
|
434
|
+
transportMode: 'websocket',
|
|
488
435
|
}}
|
|
489
436
|
>
|
|
490
437
|
<App />
|
|
491
438
|
</FlagmintProvider>
|
|
492
439
|
</React.StrictMode>
|
|
493
|
-
)
|
|
440
|
+
);
|
|
494
441
|
```
|
|
495
442
|
|
|
496
443
|
### Remix
|
|
497
444
|
|
|
498
445
|
```tsx
|
|
499
446
|
// app/root.tsx
|
|
500
|
-
import { FlagmintProvider } from 'react-
|
|
447
|
+
import { FlagmintProvider } from 'flagmint-react-sdk/client';
|
|
501
448
|
|
|
502
449
|
export default function App() {
|
|
503
450
|
return (
|
|
@@ -507,10 +454,10 @@ export default function App() {
|
|
|
507
454
|
<Links />
|
|
508
455
|
</head>
|
|
509
456
|
<body>
|
|
510
|
-
<FlagmintProvider
|
|
457
|
+
<FlagmintProvider
|
|
511
458
|
options={{
|
|
512
459
|
apiKey: process.env.FLAGMINT_API_KEY!,
|
|
513
|
-
transportMode: 'long-polling'
|
|
460
|
+
transportMode: 'long-polling', // Recommended for Remix
|
|
514
461
|
}}
|
|
515
462
|
>
|
|
516
463
|
<Outlet />
|
|
@@ -518,73 +465,43 @@ export default function App() {
|
|
|
518
465
|
<Scripts />
|
|
519
466
|
</body>
|
|
520
467
|
</html>
|
|
521
|
-
)
|
|
468
|
+
);
|
|
522
469
|
}
|
|
523
470
|
```
|
|
524
471
|
|
|
472
|
+
---
|
|
473
|
+
|
|
525
474
|
## ⚡ Performance
|
|
526
475
|
|
|
527
476
|
### Fine-grained Reactivity
|
|
528
477
|
|
|
529
|
-
Only
|
|
478
|
+
Only the component reading a specific flag re-renders when that flag changes:
|
|
530
479
|
|
|
531
480
|
```tsx
|
|
532
|
-
// ✅
|
|
481
|
+
// ✅ Only re-renders when 'new_dashboard' changes
|
|
533
482
|
function ComponentA() {
|
|
534
|
-
const
|
|
535
|
-
return <div>{
|
|
483
|
+
const showDashboard = useFlag('new_dashboard', false);
|
|
484
|
+
return <div>{showDashboard && <NewDashboard />}</div>;
|
|
536
485
|
}
|
|
537
486
|
|
|
538
|
-
// ✅
|
|
487
|
+
// ✅ Only re-renders when 'results_per_page' changes
|
|
539
488
|
function ComponentB() {
|
|
540
|
-
const
|
|
541
|
-
return <
|
|
489
|
+
const limit = useFlag('results_per_page', 10);
|
|
490
|
+
return <ResultsList limit={limit} />;
|
|
542
491
|
}
|
|
543
492
|
|
|
544
|
-
//
|
|
493
|
+
// ⚠️ Re-renders when ANY flag changes - use sparingly
|
|
545
494
|
function ComponentAll() {
|
|
546
|
-
const
|
|
547
|
-
return <div>{
|
|
495
|
+
const flags = useFlags();
|
|
496
|
+
return <div>{flags['new_dashboard'] && <NewDashboard />}</div>;
|
|
548
497
|
}
|
|
549
498
|
```
|
|
550
499
|
|
|
551
|
-
###
|
|
552
|
-
|
|
553
|
-
- **react-flagmint**: ~3KB gzipped
|
|
554
|
-
- **zustand**: ~2KB gzipped
|
|
555
|
-
- **Total overhead**: ~5KB gzipped
|
|
556
|
-
|
|
557
|
-
## 🔧 Development
|
|
558
|
-
|
|
559
|
-
### Local Development
|
|
560
|
-
|
|
561
|
-
```bash
|
|
562
|
-
# Clone the monorepo
|
|
563
|
-
git clone <repo-url>
|
|
564
|
-
cd flagmint-monorepo
|
|
565
|
-
|
|
566
|
-
# Install dependencies
|
|
567
|
-
pnpm install
|
|
568
|
-
|
|
569
|
-
# Build the library
|
|
570
|
-
pnpm --filter react-flagmint build
|
|
571
|
-
|
|
572
|
-
# Run the Next.js example
|
|
573
|
-
pnpm dev:next
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### Testing
|
|
500
|
+
### Offline Cache
|
|
577
501
|
|
|
578
|
-
|
|
579
|
-
# Run tests
|
|
580
|
-
pnpm test
|
|
581
|
-
|
|
582
|
-
# Type checking
|
|
583
|
-
pnpm typecheck
|
|
502
|
+
Flags are cached in `localStorage` by default (`enableOfflineCache: true`). On the next page load, cached flags are served immediately while the fresh values load in the background - eliminating the loading state entirely for returning users.
|
|
584
503
|
|
|
585
|
-
|
|
586
|
-
pnpm lint
|
|
587
|
-
```
|
|
504
|
+
---
|
|
588
505
|
|
|
589
506
|
## 📄 License
|
|
590
507
|
|
|
@@ -594,16 +511,15 @@ MIT
|
|
|
594
511
|
|
|
595
512
|
1. Fork the repository
|
|
596
513
|
2. Create a feature branch
|
|
597
|
-
3. Make your changes
|
|
598
|
-
4.
|
|
599
|
-
5. Submit a pull request
|
|
514
|
+
3. Make your changes with tests
|
|
515
|
+
4. Submit a pull request
|
|
600
516
|
|
|
601
517
|
## 📞 Support
|
|
602
518
|
|
|
603
519
|
- 📧 Email: support@flagmint.com
|
|
604
|
-
- 🐛 Issues: [GitHub Issues](https://github.com/
|
|
605
|
-
- 📖 Docs: [
|
|
520
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/flagmint/flagmint-react-sdk/issues)
|
|
521
|
+
- 📖 Docs: [docs.flagmint.com](https://docs.flagmint.com)
|
|
606
522
|
|
|
607
523
|
---
|
|
608
524
|
|
|
609
|
-
**Made with ❤️ by the Flagmint team**
|
|
525
|
+
**Made with ❤️ by the Flagmint team**
|