flagmint-react-sdk 0.7.13 → 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 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 flags re-render when those flags change
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
- - 🔀 **Dual export strategy** - Separate server-safe types and client-only hooks
11
- - **Auto-initialization** - Direct integration with FlagClient from flagmint-js-sdk
12
- - 🔐 **Deferred initialization** - Perfect for authentication flows
13
- - 📱 **Framework agnostic** - Works with Next.js, Remix, Vite, and more
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-flagmint flagmint-js-sdk
18
+ npm install flagmint-react-sdk flagmint-js-sdk
19
19
  # or
20
- pnpm add react-flagmint flagmint-js-sdk
20
+ yarn add flagmint-react-sdk flagmint-js-sdk
21
21
  # or
22
- yarn add react-flagmint flagmint-js-sdk
22
+ pnpm add flagmint-react-sdk flagmint-js-sdk
23
23
  ```
24
24
 
25
25
  ## 🚀 Quick Start
26
26
 
27
- ### 1. Basic Setup
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 (Next.js App Router)
31
- 'use client'
32
- import { FlagmintProvider } from 'react-flagmint/client'
32
+ // app/providers.tsx
33
+ 'use client';
34
+ import { FlagmintProvider } from 'flagmint-react-sdk/client';
33
35
 
34
- export default function Providers({ children }: { children: React.ReactNode }) {
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, useFlags, useFlagmintReady } from 'react-flagmint/client'
73
+ 'use client';
74
+ import { useFlag, useFlagmintReady } from 'flagmint-react-sdk/client';
79
75
 
80
76
  export default function FeatureComponent() {
81
- const showNewFeature = useFlag('new-dashboard', false)
82
- const userPlan = useFlag('user-plan', 'free')
83
- const isReady = useFlagmintReady()
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 flags...</div>
83
+ return <div>Loading...</div>;
87
84
  }
88
-
85
+
89
86
  return (
90
- <div>
91
- {showNewFeature && <NewDashboard />}
92
- <div>Current plan: {userPlan}</div>
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
- The provider component that initializes the FlagClient and provides flag context to your app.
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/hydration |
120
- | `deferInitialization` | `boolean` | `false` | If true, wait for manual initialization |
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
- ```tsx
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
- Get a specific flag value with fine-grained reactivity.
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('feature-name', false)
146
- const userTier = useFlag('user-tier', 'free')
147
- const config = useFlag('app-config', { theme: 'light' })
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
- **Parameters:**
151
- - `key: string` - The flag key
152
- - `fallback?: T` - Default value if flag is not found
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
- **Returns:** The flag value or fallback
224
+ Returns all evaluated flags as a reactive object. Re-renders when **any** flag changes.
155
225
 
156
- #### useFlags()
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
- Get all currently loaded flags.
228
+ Returns `{}` silently if called outside a `FlagmintProvider`.
159
229
 
160
230
  ```tsx
161
- const allFlags = useFlags()
162
- console.log(allFlags) // { 'feature-a': true, 'user-tier': 'pro' }
231
+ const flags = useFlags();
232
+ // { new_dashboard: true, color_scheme: 'dark', results_per_page: 30 }
163
233
  ```
164
234
 
165
- **Returns:** Object containing all loaded flags
166
-
167
- #### useFlagmint()
235
+ ---
168
236
 
169
- Get access to the FlagClient instance and utilities.
237
+ #### `useFlagmint()`
170
238
 
171
- ```tsx
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
- // Update user context
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
- client: FlagClient | null
182
- isReady: boolean
183
- isInitialized: boolean
184
- updateContext: (context: Record<string, any>) => Promise<void>
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
- #### useFlagmintReady()
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
- Check if the FlagClient is ready to serve flags.
191
-
192
- ```tsx
193
- const isReady = useFlagmintReady()
260
+ ---
194
261
 
195
- if (!isReady) {
196
- return <LoadingSpinner />
197
- }
198
- ```
262
+ #### `useFlagmintReady()`
199
263
 
200
- #### useFlagClient()
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
- Get direct access to the FlagClient instance.
266
+ Returns `false` silently if called outside a `FlagmintProvider`.
203
267
 
204
268
  ```tsx
205
- const client = useFlagClient<MyFlagTypes>()
206
- const specificFlag = client?.getFlag('feature-x', false)
269
+ const isReady = useFlagmintReady();
270
+
271
+ if (!isReady) return <Skeleton />;
272
+
273
+ return showNewPricing ? <NewPricing /> : <OldPricing />;
207
274
  ```
208
275
 
209
- ## 🔧 Usage Patterns
276
+ ---
277
+
278
+ #### `useFlagmintStore()` *(Advanced)*
210
279
 
211
- ### 1. Auto-initialization (Default)
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
- Best when user context is available immediately:
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
- function App() {
217
- return (
218
- <FlagmintProvider
219
- options={{
220
- apiKey: 'your-api-key',
221
- context: {
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
- ### 2. Deferred Initialization
294
+ ---
234
295
 
235
- Perfect for authentication flows:
296
+ ### Hook Reference Summary
236
297
 
237
- ```tsx
238
- // Provider with deferred initialization
239
- <FlagmintProvider
240
- options={{
241
- apiKey: 'your-api-key',
242
- context: {} // Empty initially
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
- // Login component
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
- ### 3. TypeScript Usage
308
+ ## 🔧 Usage Patterns
270
309
 
271
- Define your flag types for better type safety:
310
+ ### TypeScript: Typed Flags
272
311
 
273
312
  ```tsx
274
313
  // types/flags.ts
275
314
  export interface AppFlags {
276
- 'show-beta-feature': boolean
277
- 'user-plan': 'free' | 'pro' | 'enterprise'
278
- 'theme-config': {
279
- primaryColor: string
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
- // components/TypedComponent.tsx
291
- import { useFlag } from 'react-flagmint/client'
292
- import type { AppFlags } from '../types/flags'
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
- ### 4. Server-Side Rendering (SSR)
311
-
312
- #### Next.js App Router
326
+ ### Updating Context After Login
313
327
 
314
328
  ```tsx
315
- // app/providers.tsx
316
- 'use client'
317
- import { FlagmintProvider } from 'react-flagmint/client'
318
-
319
- export default function Providers({ children, initialFlags = {} }) {
320
- return (
321
- <FlagmintProvider
322
- options={{
323
- apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
324
- transportMode: 'websocket'
325
- }}
326
- initialFlags={initialFlags}
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
- // app/page.tsx
334
- import { FlagClient } from 'flagmint-js-sdk'
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
- ### 5. Context Updates
362
-
363
- Update user context dynamically:
348
+ ### Preventing Flash of Wrong Content
364
349
 
365
350
  ```tsx
366
- function UserSettings() {
367
- const { updateContext } = useFlagmint()
368
- const currentTheme = useFlag('user-theme', 'light')
369
-
370
- const handleThemeChange = async (newTheme: string) => {
371
- // Update context - flags will be re-evaluated
372
- await updateContext({ theme: newTheme })
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
- ### 6. Loading States
361
+ ### Preview Mode (Local Evaluation)
390
362
 
391
- Handle loading states gracefully:
363
+ Use preview mode in Storybook, tests, or demos to evaluate flags locally without a server connection:
392
364
 
393
365
  ```tsx
394
- function FeaturePage() {
395
- const isReady = useFlagmintReady()
396
- const showPremiumFeature = useFlag('premium-feature', false)
397
-
398
- if (!isReady) {
399
- return (
400
- <div className="loading-container">
401
- <Spinner />
402
- <p>Loading personalized features...</p>
403
- </div>
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
- ### 7. Error Handling
381
+ ### Error Handling
421
382
 
422
383
  ```tsx
423
- <FlagmintProvider
384
+ <FlagmintProvider
424
385
  options={{
425
- apiKey: 'your-api-key',
386
+ apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
426
387
  onError: (error) => {
427
- console.error('Flagmint error:', error)
428
- // Send to error reporting service
429
- errorReporting.captureException(error)
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 (App Router)
401
+ ### Next.js App Router
440
402
 
441
403
  ```tsx
442
404
  // app/layout.tsx
443
- import Providers from './providers'
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
- <Providers>{children}</Providers>
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-flagmint/client'
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-flagmint/client'
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 components that use specific flags will re-render when those flags change:
478
+ Only the component reading a specific flag re-renders when that flag changes:
530
479
 
531
480
  ```tsx
532
- // ✅ Good: Only re-renders when 'feature-a' changes
481
+ // ✅ Only re-renders when 'new_dashboard' changes
533
482
  function ComponentA() {
534
- const featureA = useFlag('feature-a', false)
535
- return <div>{featureA && <FeatureA />}</div>
483
+ const showDashboard = useFlag('new_dashboard', false);
484
+ return <div>{showDashboard && <NewDashboard />}</div>;
536
485
  }
537
486
 
538
- // ✅ Good: Only re-renders when 'feature-b' changes
487
+ // ✅ Only re-renders when 'results_per_page' changes
539
488
  function ComponentB() {
540
- const featureB = useFlag('feature-b', false)
541
- return <div>{featureB && <FeatureB />}</div>
489
+ const limit = useFlag('results_per_page', 10);
490
+ return <ResultsList limit={limit} />;
542
491
  }
543
492
 
544
- // Avoid: Re-renders when ANY flag changes
493
+ // ⚠️ Re-renders when ANY flag changes - use sparingly
545
494
  function ComponentAll() {
546
- const allFlags = useFlags()
547
- return <div>{allFlags['feature-a'] && <FeatureA />}</div>
495
+ const flags = useFlags();
496
+ return <div>{flags['new_dashboard'] && <NewDashboard />}</div>;
548
497
  }
549
498
  ```
550
499
 
551
- ### Bundle Size
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
- ```bash
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
- # Lint
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. Add tests
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/jtad009/flagmint-react-sdk/issues)
605
- - 📖 Docs: [Documentation](https://docs.flagmint.com)
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**