@featureflare/react 0.0.5 → 0.0.6-beta.261
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 +119 -83
- package/dist/index.cjs +417 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -3
- package/dist/index.d.ts +61 -3
- package/dist/index.js +417 -68
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -12,150 +12,186 @@ pnpm add @featureflare/react
|
|
|
12
12
|
yarn add @featureflare/react
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
**Peer Dependencies:**
|
|
16
|
-
|
|
17
|
-
- `react >= 18`
|
|
18
|
-
- `react-dom >= 18`
|
|
19
|
-
|
|
20
15
|
## Usage
|
|
21
16
|
|
|
22
|
-
### Setup
|
|
23
|
-
|
|
24
|
-
Wrap your app with `FeatureFlareProvider`:
|
|
17
|
+
### Minimal Setup
|
|
25
18
|
|
|
26
19
|
```typescript
|
|
27
|
-
import { FeatureFlareProvider } from '@featureflare/react';
|
|
20
|
+
import { FeatureFlareProvider, resolveFeatureFlareBrowserConfig, useFlags } from '@featureflare/react';
|
|
21
|
+
|
|
22
|
+
function FlagsBootstrap() {
|
|
23
|
+
useFlags({
|
|
24
|
+
user: { id: 'user-123', email: 'dev@company.com' },
|
|
25
|
+
defaultValue: false,
|
|
26
|
+
refreshIntervalMs: 10000,
|
|
27
|
+
pauseWhenHidden: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
28
32
|
|
|
29
33
|
export function App() {
|
|
34
|
+
const config = resolveFeatureFlareBrowserConfig();
|
|
35
|
+
|
|
36
|
+
if (!config.sdkKey) {
|
|
37
|
+
return <div>Feature flags disabled</div>;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
return (
|
|
31
41
|
<FeatureFlareProvider
|
|
32
42
|
config={{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
...config,
|
|
44
|
+
bootstrap: {
|
|
45
|
+
flags: { 'new-nav': true }
|
|
46
|
+
}
|
|
37
47
|
}}
|
|
38
|
-
initialUser={{ id: 'user-123' }}
|
|
48
|
+
initialUser={{ id: 'user-123', key: 'user-123' }}
|
|
39
49
|
>
|
|
40
|
-
|
|
50
|
+
<FlagsBootstrap />
|
|
51
|
+
{/* app */}
|
|
41
52
|
</FeatureFlareProvider>
|
|
42
53
|
);
|
|
43
54
|
}
|
|
44
55
|
```
|
|
45
56
|
|
|
46
|
-
### Read
|
|
47
|
-
|
|
48
|
-
Use the `useBoolFlag` hook to read boolean flags:
|
|
57
|
+
### Read One Flag
|
|
49
58
|
|
|
50
59
|
```typescript
|
|
51
|
-
import {
|
|
60
|
+
import { useFlag } from '@featureflare/react';
|
|
52
61
|
|
|
53
62
|
export function NewNav() {
|
|
54
|
-
const { value, loading, error } =
|
|
55
|
-
|
|
63
|
+
const { value, loading, error } = useFlag('new-nav', false);
|
|
64
|
+
|
|
56
65
|
if (loading) return <div>Loading...</div>;
|
|
57
|
-
if (error) return <div>Error: {error
|
|
58
|
-
|
|
66
|
+
if (error) return <div>Error: {error}</div>;
|
|
67
|
+
|
|
59
68
|
return value ? <div>New nav ON</div> : <div>New nav OFF</div>;
|
|
60
69
|
}
|
|
61
70
|
```
|
|
62
71
|
|
|
63
|
-
###
|
|
72
|
+
### SSR/HTML Bootstrap (anti-flicker working flow)
|
|
73
|
+
|
|
74
|
+
Server-side (example in Next.js `getServerSideProps`):
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { FeatureFlareClient } from '@featureflare/sdk-js';
|
|
78
|
+
|
|
79
|
+
export async function getServerSideProps() {
|
|
80
|
+
const user = { id: 'user-123', key: 'user-123' };
|
|
81
|
+
const sdk = new FeatureFlareClient({
|
|
82
|
+
apiBaseUrl: process.env.FEATUREFLARE_API_BASE_URL,
|
|
83
|
+
sdkKey: process.env.FEATUREFLARE_SERVER_SDK_KEY
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const nav = await sdk.bool('new-nav', user, false);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
props: {
|
|
90
|
+
user,
|
|
91
|
+
ffBootstrap: {
|
|
92
|
+
flags: { 'new-nav': nav }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
```
|
|
64
98
|
|
|
65
|
-
|
|
99
|
+
Client-side provider wiring:
|
|
66
100
|
|
|
67
101
|
```typescript
|
|
68
|
-
import {
|
|
102
|
+
import { FeatureFlareProvider, resolveFeatureFlareBrowserConfig } from '@featureflare/react';
|
|
69
103
|
|
|
70
|
-
export function
|
|
71
|
-
const
|
|
104
|
+
export default function Page({ user, ffBootstrap }) {
|
|
105
|
+
const config = resolveFeatureFlareBrowserConfig();
|
|
72
106
|
|
|
73
107
|
return (
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
108
|
+
<FeatureFlareProvider
|
|
109
|
+
config={{
|
|
110
|
+
...config,
|
|
111
|
+
bootstrap: ffBootstrap
|
|
112
|
+
}}
|
|
113
|
+
initialUser={user}
|
|
81
114
|
>
|
|
82
|
-
|
|
83
|
-
|
|
115
|
+
{/* First render uses bootstrap synchronously; no loading flicker for these flags */}
|
|
116
|
+
<App />
|
|
117
|
+
</FeatureFlareProvider>
|
|
84
118
|
);
|
|
85
119
|
}
|
|
86
120
|
```
|
|
87
121
|
|
|
88
|
-
|
|
122
|
+
For non-SSR apps, inject a bootstrap JSON payload into HTML before app startup and pass it to `config.bootstrap` the same way.
|
|
123
|
+
|
|
124
|
+
## API
|
|
89
125
|
|
|
90
126
|
### `FeatureFlareProvider`
|
|
91
127
|
|
|
92
|
-
|
|
128
|
+
Props:
|
|
129
|
+
|
|
130
|
+
- `config: FeatureFlareReactConfig`
|
|
131
|
+
- `initialUser: FeatureFlareUserPayload`
|
|
132
|
+
- `user?: FeatureFlareUserPayload` (controlled mode)
|
|
133
|
+
- `onUserChange?: (user: FeatureFlareUserPayload) => void` (required in controlled mode)
|
|
134
|
+
- `children: React.ReactNode`
|
|
93
135
|
|
|
94
|
-
|
|
136
|
+
### `resolveFeatureFlareBrowserConfig(input?)`
|
|
95
137
|
|
|
96
|
-
|
|
97
|
-
- `apiBaseUrl?: string` - Optional explicit FeatureFlare API base URL.
|
|
98
|
-
- `sdkKey?: string` - Client SDK key (recommended). If not provided, reads from `FEATUREFLARE_CLIENT_KEY` env var.
|
|
99
|
-
- `projectKey?: string` - Legacy: project key (requires `envKey`). Not recommended.
|
|
100
|
-
- `envKey?: string` - Environment key (default: `'production'`). Only used with `projectKey`.
|
|
101
|
-
- `initialUser: FeatureFlareUserPayload` - Initial user payload
|
|
102
|
-
- `user?: FeatureFlareUserPayload` - Controlled user (requires `onUserChange`)
|
|
103
|
-
- `onUserChange?: (user: FeatureFlareUserPayload) => void` - Callback for user changes (required if using controlled `user`)
|
|
104
|
-
- `children: React.ReactNode` - Your app components
|
|
138
|
+
Builds provider config from common `NEXT_PUBLIC_FEATUREFLARE_*` vars.
|
|
105
139
|
|
|
106
|
-
|
|
140
|
+
Returns:
|
|
107
141
|
|
|
108
|
-
|
|
142
|
+
- `apiBaseUrl?: string`
|
|
143
|
+
- `envKey: 'development' | 'staging' | 'production'`
|
|
144
|
+
- `sdkKey?: string`
|
|
109
145
|
|
|
110
|
-
|
|
146
|
+
### `useFeatureFlareUser()`
|
|
111
147
|
|
|
112
|
-
|
|
113
|
-
- `loading: boolean` - Whether the evaluation is in progress
|
|
114
|
-
- `error: Error | null` - Error if evaluation failed
|
|
148
|
+
Returns:
|
|
115
149
|
|
|
116
|
-
|
|
150
|
+
- `[user, setUser]`
|
|
117
151
|
|
|
118
|
-
|
|
119
|
-
const { value, loading, error } = useBoolFlag('feature-flag', false);
|
|
120
|
-
```
|
|
152
|
+
### `useFlags(...)`
|
|
121
153
|
|
|
122
|
-
|
|
154
|
+
Signatures:
|
|
123
155
|
|
|
124
|
-
|
|
156
|
+
- `useFlags(keys: string[], defaultValue?: boolean)` → `{ values, loading, errors }`
|
|
157
|
+
- `useFlags(input?: { user?: FeatureFlareUserPayload; defaultValue?: boolean; refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })`
|
|
158
|
+
- `useFlags(defaultValue?: boolean, options?: { refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })`
|
|
125
159
|
|
|
126
|
-
|
|
160
|
+
### `useBoolFlags(...)`
|
|
127
161
|
|
|
128
|
-
|
|
162
|
+
Signatures:
|
|
129
163
|
|
|
130
|
-
|
|
164
|
+
- `useBoolFlags(keys: string[], defaultValue?: boolean)` → `{ values, loading, errors }`
|
|
165
|
+
- `useBoolFlags(defaultValue?: boolean, options?: { refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })` → `{ values, loading, errors }` (all flags)
|
|
131
166
|
|
|
132
|
-
|
|
133
|
-
const [user, setUser] = useFeatureFlareUser();
|
|
134
|
-
```
|
|
167
|
+
Returns:
|
|
135
168
|
|
|
136
|
-
|
|
169
|
+
- `flags: Array<{ key: string; value: boolean }>`
|
|
170
|
+
- `loading: boolean`
|
|
171
|
+
- `error: string | null`
|
|
137
172
|
|
|
138
|
-
|
|
173
|
+
### `useFlag(key, defaultValue?)`
|
|
139
174
|
|
|
140
|
-
-
|
|
175
|
+
Selector-based single-flag subscription. Components only re-render when that flag changes.
|
|
141
176
|
|
|
142
|
-
|
|
143
|
-
// This will use FEATUREFLARE_CLIENT_KEY from env if sdkKey is not provided
|
|
144
|
-
<FeatureFlareProvider config={{}} initialUser={{ id: 'user-123' }}>
|
|
145
|
-
{/* ... */}
|
|
146
|
-
</FeatureFlareProvider>
|
|
147
|
-
```
|
|
177
|
+
### `useFlagDiagnostics(key)`
|
|
148
178
|
|
|
149
|
-
|
|
179
|
+
Returns per-flag runtime diagnostics (source, stale state, cache timestamps, latency).
|
|
150
180
|
|
|
151
|
-
|
|
181
|
+
Behavior:
|
|
152
182
|
|
|
153
|
-
|
|
183
|
+
- Immediate fetch on mount/user change.
|
|
184
|
+
- Shared polling via provider context (multiple consumers do not duplicate requests).
|
|
185
|
+
- Optional polling controls with hidden-tab awareness.
|
|
186
|
+
- If `user` is provided in input form, hook syncs provider user automatically.
|
|
154
187
|
|
|
155
|
-
|
|
188
|
+
## Notes
|
|
156
189
|
|
|
157
|
-
|
|
190
|
+
- Use client SDK keys in browser apps.
|
|
191
|
+
- If `config.sdkKey` is missing, skip initializing the provider.
|
|
158
192
|
|
|
159
|
-
##
|
|
193
|
+
## Security: Client-side flags are not authorization
|
|
160
194
|
|
|
161
|
-
|
|
195
|
+
Client keys can be extracted from your frontend bundle. **Never gate truly sensitive operations solely with client-evaluated flags**—enforce authorization on your backend.
|
|
196
|
+
- `config.bootstrap?: { flags, killSwitches }` for SSR/HTML bootstrap to avoid first-render flicker
|
|
197
|
+
- `config.timeoutMs | maxRetries | backoffMs | jitter | cacheTtlMs | staleTtlMs | realtime` are forwarded to the SDK client
|