@flagify/react 1.0.0
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 +352 -0
- package/dist/index.d.mts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +133 -0
- package/dist/index.mjs +99 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://flagify.dev">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://flagify.dev/logo-white.svg" />
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="https://flagify.dev/logo-color.svg" />
|
|
6
|
+
<img alt="Flagify" src="https://flagify.dev/logo-color.svg" width="280" />
|
|
7
|
+
</picture>
|
|
8
|
+
</a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<strong>Feature flags for modern teams</strong>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://www.npmjs.com/package/@flagify/react"><img src="https://img.shields.io/npm/v/@flagify/react.svg?style=flat-square&color=0D80F9" alt="npm version" /></a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/@flagify/react"><img src="https://img.shields.io/npm/dm/@flagify/react.svg?style=flat-square&color=0D80F9" alt="npm downloads" /></a>
|
|
18
|
+
<a href="https://github.com/flagifyhq/react-sdk/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@flagify/react.svg?style=flat-square&color=0D80F9" alt="license" /></a>
|
|
19
|
+
<a href="https://github.com/flagifyhq/react-sdk"><img src="https://img.shields.io/github/stars/flagifyhq/react-sdk?style=flat-square&color=0D80F9" alt="github stars" /></a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<a href="https://flagify.dev/docs">Documentation</a> ·
|
|
24
|
+
<a href="https://flagify.dev/docs/sdks/react">SDK Reference</a> ·
|
|
25
|
+
<a href="https://github.com/flagifyhq/react-sdk/issues">Issues</a> ·
|
|
26
|
+
<a href="https://flagify.dev">Website</a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
`@flagify/react` is the official React SDK for [Flagify](https://flagify.dev). Idiomatic hooks and a context provider for feature flag evaluation in React applications.
|
|
34
|
+
|
|
35
|
+
- **Hooks-first** -- `useFlag`, `useVariant`, `useFlagValue` for every use case
|
|
36
|
+
- **Type-safe** -- Full TypeScript generics for flag values
|
|
37
|
+
- **Zero config** -- Wrap with `<FlagifyProvider>`, use hooks anywhere
|
|
38
|
+
- **Lightweight** -- Thin wrapper over [`@flagify/node`](https://github.com/flagifyhq/node-sdk)
|
|
39
|
+
- **React 18+** -- Built for modern React
|
|
40
|
+
- **React Native ready** -- Works in React Native and Expo with zero additional setup
|
|
41
|
+
|
|
42
|
+
## Table of contents
|
|
43
|
+
|
|
44
|
+
- [Installation](#installation)
|
|
45
|
+
- [Quick start](#quick-start)
|
|
46
|
+
- [Provider](#provider)
|
|
47
|
+
- [Hooks](#hooks)
|
|
48
|
+
- [`useFlag`](#useflagflagkey-string-boolean)
|
|
49
|
+
- [`useVariant`](#usevariantflagkey-string-string--undefined)
|
|
50
|
+
- [`useFlagValue`](#useflagvaluetflagkey-string-t--undefined)
|
|
51
|
+
- [`useFlagifyClient`](#useflagifyclient-flagify)
|
|
52
|
+
- [Examples](#examples)
|
|
53
|
+
- [API reference](#api-reference)
|
|
54
|
+
- [Contributing](#contributing)
|
|
55
|
+
- [License](#license)
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# pnpm
|
|
61
|
+
pnpm add @flagify/react
|
|
62
|
+
|
|
63
|
+
# npm
|
|
64
|
+
npm install @flagify/react
|
|
65
|
+
|
|
66
|
+
# yarn
|
|
67
|
+
yarn add @flagify/react
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Peer dependency:** React 18+ is required.
|
|
71
|
+
|
|
72
|
+
## React Native / Expo
|
|
73
|
+
|
|
74
|
+
`@flagify/react` is fully compatible with React Native (0.64+) and Expo (SDK 44+). No separate package or polyfills needed.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx expo install @flagify/react
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Wrap your root with `<FlagifyProvider>` and use hooks anywhere. For a full getting-started guide, see the [React Native documentation](https://flagify.dev/docs/sdks/react-native).
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
**1. Wrap your app with the provider**
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { FlagifyProvider } from '@flagify/react'
|
|
88
|
+
|
|
89
|
+
function App() {
|
|
90
|
+
return (
|
|
91
|
+
<FlagifyProvider projectKey="proj_xxx" publicKey="pk_xxx">
|
|
92
|
+
<YourApp />
|
|
93
|
+
</FlagifyProvider>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**2. Use hooks in any component**
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { useFlag } from '@flagify/react'
|
|
102
|
+
|
|
103
|
+
function Navbar() {
|
|
104
|
+
const showBanner = useFlag('promo-banner')
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<nav>
|
|
108
|
+
{showBanner && <PromoBanner />}
|
|
109
|
+
</nav>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Provider
|
|
115
|
+
|
|
116
|
+
### `<FlagifyProvider>`
|
|
117
|
+
|
|
118
|
+
Initializes the Flagify client and provides it to all child components via React context.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<FlagifyProvider
|
|
122
|
+
projectKey="proj_xxx"
|
|
123
|
+
publicKey="pk_xxx"
|
|
124
|
+
options={{
|
|
125
|
+
apiUrl: 'https://api.flagify.dev',
|
|
126
|
+
staleTimeMs: 300_000,
|
|
127
|
+
user: {
|
|
128
|
+
id: 'user_123',
|
|
129
|
+
email: 'mario@example.com',
|
|
130
|
+
role: 'admin',
|
|
131
|
+
geolocation: { country: 'US' },
|
|
132
|
+
},
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{children}
|
|
136
|
+
</FlagifyProvider>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Props
|
|
140
|
+
|
|
141
|
+
All props from [`FlagifyOptions`](https://github.com/flagifyhq/node-sdk#configuration-options) are supported:
|
|
142
|
+
|
|
143
|
+
| Prop | Type | Required | Description |
|
|
144
|
+
|------|------|----------|-------------|
|
|
145
|
+
| `projectKey` | `string` | Yes | Project identifier from your Flagify workspace |
|
|
146
|
+
| `publicKey` | `string` | Yes | Client-safe publishable API key |
|
|
147
|
+
| `secretKey` | `string` | No | Server-side secret key |
|
|
148
|
+
| `options` | `object` | No | Additional configuration (apiUrl, staleTimeMs, user, realtime) |
|
|
149
|
+
| `children` | `ReactNode` | Yes | Your application tree |
|
|
150
|
+
|
|
151
|
+
#### Context value
|
|
152
|
+
|
|
153
|
+
The provider exposes the following context:
|
|
154
|
+
|
|
155
|
+
| Property | Type | Description |
|
|
156
|
+
|----------|------|-------------|
|
|
157
|
+
| `client` | `Flagify \| null` | The underlying Flagify client instance |
|
|
158
|
+
| `isReady` | `boolean` | `true` once the client has been initialized |
|
|
159
|
+
|
|
160
|
+
## Hooks
|
|
161
|
+
|
|
162
|
+
### `useFlag(flagKey: string): boolean`
|
|
163
|
+
|
|
164
|
+
Evaluates a boolean feature flag. Returns `false` if the flag doesn't exist or is disabled.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
function Dashboard() {
|
|
168
|
+
const isNew = useFlag('new-dashboard')
|
|
169
|
+
|
|
170
|
+
if (!isNew) return <LegacyDashboard />
|
|
171
|
+
return <NewDashboard />
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### `useVariant(flagKey: string): string | undefined`
|
|
178
|
+
|
|
179
|
+
Returns the string variant of a multivariate flag. Ideal for A/B tests and experiments.
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
function Onboarding() {
|
|
183
|
+
const variant = useVariant('onboarding-flow')
|
|
184
|
+
|
|
185
|
+
switch (variant) {
|
|
186
|
+
case 'control': return <OnboardingClassic />
|
|
187
|
+
case 'variant-a': return <OnboardingShort />
|
|
188
|
+
case 'variant-b': return <OnboardingGuided />
|
|
189
|
+
default: return <OnboardingClassic />
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### `useFlagValue<T>(flagKey: string): T | undefined`
|
|
197
|
+
|
|
198
|
+
Returns a typed flag value with full TypeScript generics. Supports `number`, `string`, `boolean`, and `JSON` values.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
interface ListConfig {
|
|
202
|
+
maxItems: number
|
|
203
|
+
showPagination: boolean
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function ItemList() {
|
|
207
|
+
const config = useFlagValue<ListConfig>('list-config')
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<ul>
|
|
211
|
+
{items.slice(0, config?.maxItems ?? 10).map(item => (
|
|
212
|
+
<li key={item.id}>{item.name}</li>
|
|
213
|
+
))}
|
|
214
|
+
</ul>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### `useFlagifyClient(): Flagify`
|
|
222
|
+
|
|
223
|
+
Direct access to the underlying [`Flagify`](https://github.com/flagifyhq/node-sdk) client instance. Throws if used outside of `<FlagifyProvider>`.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
function FeatureGate({ flagKey, children }: { flagKey: string; children: ReactNode }) {
|
|
227
|
+
const client = useFlagifyClient()
|
|
228
|
+
|
|
229
|
+
if (!client.isEnabled(flagKey)) return null
|
|
230
|
+
return <>{children}</>
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Examples
|
|
235
|
+
|
|
236
|
+
### Feature gate component
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { useFlag } from '@flagify/react'
|
|
240
|
+
import type { ReactNode } from 'react'
|
|
241
|
+
|
|
242
|
+
function FeatureGate({ flag, children, fallback }: {
|
|
243
|
+
flag: string
|
|
244
|
+
children: ReactNode
|
|
245
|
+
fallback?: ReactNode
|
|
246
|
+
}) {
|
|
247
|
+
const isEnabled = useFlag(flag)
|
|
248
|
+
return <>{isEnabled ? children : fallback}</>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Usage
|
|
252
|
+
<FeatureGate flag="premium-features" fallback={<UpgradePrompt />}>
|
|
253
|
+
<PremiumDashboard />
|
|
254
|
+
</FeatureGate>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### A/B test with analytics
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
import { useVariant } from '@flagify/react'
|
|
261
|
+
import { useEffect } from 'react'
|
|
262
|
+
|
|
263
|
+
function PricingPage() {
|
|
264
|
+
const variant = useVariant('pricing-layout')
|
|
265
|
+
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
analytics.track('pricing_viewed', { variant })
|
|
268
|
+
}, [variant])
|
|
269
|
+
|
|
270
|
+
return variant === 'variant-a'
|
|
271
|
+
? <PricingCards />
|
|
272
|
+
: <PricingTable />
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Remote config
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
import { useFlagValue } from '@flagify/react'
|
|
280
|
+
|
|
281
|
+
interface ThemeConfig {
|
|
282
|
+
primaryColor: string
|
|
283
|
+
borderRadius: number
|
|
284
|
+
fontFamily: string
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function ThemeProvider({ children }: { children: ReactNode }) {
|
|
288
|
+
const theme = useFlagValue<ThemeConfig>('theme-config')
|
|
289
|
+
|
|
290
|
+
const style = {
|
|
291
|
+
'--primary': theme?.primaryColor ?? '#0D80F9',
|
|
292
|
+
'--radius': `${theme?.borderRadius ?? 8}px`,
|
|
293
|
+
'--font': theme?.fontFamily ?? 'Inter',
|
|
294
|
+
} as React.CSSProperties
|
|
295
|
+
|
|
296
|
+
return <div style={style}>{children}</div>
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## API reference
|
|
301
|
+
|
|
302
|
+
| Export | Type | Description |
|
|
303
|
+
|--------|------|-------------|
|
|
304
|
+
| `FlagifyProvider` | Component | Context provider -- wraps your app |
|
|
305
|
+
| `FlagifyContext` | `React.Context` | Raw context (advanced usage) |
|
|
306
|
+
| `useFlag` | Hook | Boolean flag evaluation |
|
|
307
|
+
| `useVariant` | Hook | String variant evaluation |
|
|
308
|
+
| `useFlagValue` | Hook | Typed value evaluation with generics |
|
|
309
|
+
| `useFlagifyClient` | Hook | Direct client access |
|
|
310
|
+
| `FlagifyProviderProps` | Type | Props for `FlagifyProvider` |
|
|
311
|
+
| `FlagifyContextValue` | Type | Shape of the context value |
|
|
312
|
+
|
|
313
|
+
Types re-exported from `@flagify/node`:
|
|
314
|
+
|
|
315
|
+
| Export | Description |
|
|
316
|
+
|--------|-------------|
|
|
317
|
+
| `FlagifyOptions` | Client configuration |
|
|
318
|
+
| `FlagifyUser` | User context for targeting |
|
|
319
|
+
| `FlagifyFlaggy` | Flag data structure |
|
|
320
|
+
| `IFlagifyClient` | Client interface |
|
|
321
|
+
|
|
322
|
+
## Contributing
|
|
323
|
+
|
|
324
|
+
We welcome contributions. Please open an issue first to discuss what you'd like to change.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Clone
|
|
328
|
+
git clone https://github.com/flagifyhq/react-sdk.git
|
|
329
|
+
cd react-sdk
|
|
330
|
+
|
|
331
|
+
# Install
|
|
332
|
+
pnpm install
|
|
333
|
+
|
|
334
|
+
# Development (watch mode)
|
|
335
|
+
pnpm run dev
|
|
336
|
+
|
|
337
|
+
# Build
|
|
338
|
+
pnpm run build
|
|
339
|
+
|
|
340
|
+
# Generate barrel exports
|
|
341
|
+
pnpm run generate
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
MIT -- see [LICENSE](./LICENSE) for details.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
<p align="center">
|
|
351
|
+
<sub>Built with care by the <a href="https://flagify.dev">Flagify</a> team</sub>
|
|
352
|
+
</p>
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import * as _flagify_node from '@flagify/node';
|
|
5
|
+
import { FlagifyOptions, Flagify } from '@flagify/node';
|
|
6
|
+
|
|
7
|
+
interface FlagifyProviderProps extends FlagifyOptions {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
declare function FlagifyProvider({ children, ...config }: FlagifyProviderProps): react_jsx_runtime.JSX.Element;
|
|
11
|
+
|
|
12
|
+
interface FlagifyContextValue {
|
|
13
|
+
client: Flagify | null;
|
|
14
|
+
isReady: boolean;
|
|
15
|
+
/** Increments on every SSE flag_change event to trigger re-renders */
|
|
16
|
+
version: number;
|
|
17
|
+
}
|
|
18
|
+
/** Sentinel value — when context equals this, no provider exists */
|
|
19
|
+
declare const NO_PROVIDER: FlagifyContextValue;
|
|
20
|
+
declare const FlagifyContext: react.Context<FlagifyContextValue>;
|
|
21
|
+
|
|
22
|
+
declare function useFlag(flagKey: string): boolean;
|
|
23
|
+
|
|
24
|
+
declare function useFlagValue<T>(flagKey: string, fallback: T): T;
|
|
25
|
+
|
|
26
|
+
declare function useFlagifyClient(): _flagify_node.Flagify | null;
|
|
27
|
+
|
|
28
|
+
declare function useIsReady(): boolean;
|
|
29
|
+
|
|
30
|
+
declare function useVariant(flagKey: string, fallback: string): string;
|
|
31
|
+
|
|
32
|
+
export { FlagifyContext, type FlagifyContextValue, FlagifyProvider, type FlagifyProviderProps, NO_PROVIDER, useFlag, useFlagValue, useFlagifyClient, useIsReady, useVariant };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import * as _flagify_node from '@flagify/node';
|
|
5
|
+
import { FlagifyOptions, Flagify } from '@flagify/node';
|
|
6
|
+
|
|
7
|
+
interface FlagifyProviderProps extends FlagifyOptions {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
declare function FlagifyProvider({ children, ...config }: FlagifyProviderProps): react_jsx_runtime.JSX.Element;
|
|
11
|
+
|
|
12
|
+
interface FlagifyContextValue {
|
|
13
|
+
client: Flagify | null;
|
|
14
|
+
isReady: boolean;
|
|
15
|
+
/** Increments on every SSE flag_change event to trigger re-renders */
|
|
16
|
+
version: number;
|
|
17
|
+
}
|
|
18
|
+
/** Sentinel value — when context equals this, no provider exists */
|
|
19
|
+
declare const NO_PROVIDER: FlagifyContextValue;
|
|
20
|
+
declare const FlagifyContext: react.Context<FlagifyContextValue>;
|
|
21
|
+
|
|
22
|
+
declare function useFlag(flagKey: string): boolean;
|
|
23
|
+
|
|
24
|
+
declare function useFlagValue<T>(flagKey: string, fallback: T): T;
|
|
25
|
+
|
|
26
|
+
declare function useFlagifyClient(): _flagify_node.Flagify | null;
|
|
27
|
+
|
|
28
|
+
declare function useIsReady(): boolean;
|
|
29
|
+
|
|
30
|
+
declare function useVariant(flagKey: string, fallback: string): string;
|
|
31
|
+
|
|
32
|
+
export { FlagifyContext, type FlagifyContextValue, FlagifyProvider, type FlagifyProviderProps, NO_PROVIDER, useFlag, useFlagValue, useFlagifyClient, useIsReady, useVariant };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
FlagifyContext: () => FlagifyContext,
|
|
24
|
+
FlagifyProvider: () => FlagifyProvider,
|
|
25
|
+
NO_PROVIDER: () => NO_PROVIDER,
|
|
26
|
+
useFlag: () => useFlag,
|
|
27
|
+
useFlagValue: () => useFlagValue,
|
|
28
|
+
useFlagifyClient: () => useFlagifyClient,
|
|
29
|
+
useIsReady: () => useIsReady,
|
|
30
|
+
useVariant: () => useVariant
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/FlagifyProvider.tsx
|
|
35
|
+
var import_react2 = require("react");
|
|
36
|
+
var import_node = require("@flagify/node");
|
|
37
|
+
|
|
38
|
+
// src/context.ts
|
|
39
|
+
var import_react = require("react");
|
|
40
|
+
var NO_PROVIDER = {
|
|
41
|
+
client: null,
|
|
42
|
+
isReady: false,
|
|
43
|
+
version: -1
|
|
44
|
+
};
|
|
45
|
+
var FlagifyContext = (0, import_react.createContext)(NO_PROVIDER);
|
|
46
|
+
|
|
47
|
+
// src/FlagifyProvider.tsx
|
|
48
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
49
|
+
function FlagifyProvider({ children, ...config }) {
|
|
50
|
+
const [client, setClient] = (0, import_react2.useState)(null);
|
|
51
|
+
const [isReady, setIsReady] = (0, import_react2.useState)(false);
|
|
52
|
+
const [version, setVersion] = (0, import_react2.useState)(0);
|
|
53
|
+
const clientRef = (0, import_react2.useRef)(null);
|
|
54
|
+
const bumpVersion = (0, import_react2.useCallback)(() => {
|
|
55
|
+
setVersion((v) => v + 1);
|
|
56
|
+
}, []);
|
|
57
|
+
(0, import_react2.useEffect)(() => {
|
|
58
|
+
const instance = new import_node.Flagify(config);
|
|
59
|
+
clientRef.current = instance;
|
|
60
|
+
instance.onFlagChange = bumpVersion;
|
|
61
|
+
instance.ready().then(() => {
|
|
62
|
+
if (clientRef.current === instance) {
|
|
63
|
+
setClient(instance);
|
|
64
|
+
setIsReady(true);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
clientRef.current = null;
|
|
69
|
+
instance.onFlagChange = null;
|
|
70
|
+
instance.destroy();
|
|
71
|
+
};
|
|
72
|
+
}, [config.projectKey, config.publicKey]);
|
|
73
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlagifyContext.Provider, { value: { client, isReady, version }, children });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/useFlag.ts
|
|
77
|
+
var import_react4 = require("react");
|
|
78
|
+
|
|
79
|
+
// src/useFlagifyClient.ts
|
|
80
|
+
var import_react3 = require("react");
|
|
81
|
+
function useFlagifyClient() {
|
|
82
|
+
const ctx = (0, import_react3.useContext)(FlagifyContext);
|
|
83
|
+
if (ctx === NO_PROVIDER) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"useFlagifyClient must be used within a <FlagifyProvider>. Wrap your app with <FlagifyProvider client={client}>."
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return ctx.client;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/useFlag.ts
|
|
92
|
+
function useFlag(flagKey) {
|
|
93
|
+
const { version } = (0, import_react4.useContext)(FlagifyContext);
|
|
94
|
+
const client = useFlagifyClient();
|
|
95
|
+
void version;
|
|
96
|
+
return client?.isEnabled(flagKey) ?? false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/useFlagValue.ts
|
|
100
|
+
var import_react5 = require("react");
|
|
101
|
+
function useFlagValue(flagKey, fallback) {
|
|
102
|
+
const { version } = (0, import_react5.useContext)(FlagifyContext);
|
|
103
|
+
const client = useFlagifyClient();
|
|
104
|
+
void version;
|
|
105
|
+
return client?.getValue(flagKey, fallback) ?? fallback;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/useIsReady.ts
|
|
109
|
+
var import_react6 = require("react");
|
|
110
|
+
function useIsReady() {
|
|
111
|
+
const { isReady } = (0, import_react6.useContext)(FlagifyContext);
|
|
112
|
+
return isReady;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/useVariant.ts
|
|
116
|
+
var import_react7 = require("react");
|
|
117
|
+
function useVariant(flagKey, fallback) {
|
|
118
|
+
const { version } = (0, import_react7.useContext)(FlagifyContext);
|
|
119
|
+
const client = useFlagifyClient();
|
|
120
|
+
void version;
|
|
121
|
+
return client?.getVariant(flagKey, fallback) ?? fallback;
|
|
122
|
+
}
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
FlagifyContext,
|
|
126
|
+
FlagifyProvider,
|
|
127
|
+
NO_PROVIDER,
|
|
128
|
+
useFlag,
|
|
129
|
+
useFlagValue,
|
|
130
|
+
useFlagifyClient,
|
|
131
|
+
useIsReady,
|
|
132
|
+
useVariant
|
|
133
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/FlagifyProvider.tsx
|
|
2
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
3
|
+
import { Flagify } from "@flagify/node";
|
|
4
|
+
|
|
5
|
+
// src/context.ts
|
|
6
|
+
import { createContext } from "react";
|
|
7
|
+
var NO_PROVIDER = {
|
|
8
|
+
client: null,
|
|
9
|
+
isReady: false,
|
|
10
|
+
version: -1
|
|
11
|
+
};
|
|
12
|
+
var FlagifyContext = createContext(NO_PROVIDER);
|
|
13
|
+
|
|
14
|
+
// src/FlagifyProvider.tsx
|
|
15
|
+
import { jsx } from "react/jsx-runtime";
|
|
16
|
+
function FlagifyProvider({ children, ...config }) {
|
|
17
|
+
const [client, setClient] = useState(null);
|
|
18
|
+
const [isReady, setIsReady] = useState(false);
|
|
19
|
+
const [version, setVersion] = useState(0);
|
|
20
|
+
const clientRef = useRef(null);
|
|
21
|
+
const bumpVersion = useCallback(() => {
|
|
22
|
+
setVersion((v) => v + 1);
|
|
23
|
+
}, []);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const instance = new Flagify(config);
|
|
26
|
+
clientRef.current = instance;
|
|
27
|
+
instance.onFlagChange = bumpVersion;
|
|
28
|
+
instance.ready().then(() => {
|
|
29
|
+
if (clientRef.current === instance) {
|
|
30
|
+
setClient(instance);
|
|
31
|
+
setIsReady(true);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return () => {
|
|
35
|
+
clientRef.current = null;
|
|
36
|
+
instance.onFlagChange = null;
|
|
37
|
+
instance.destroy();
|
|
38
|
+
};
|
|
39
|
+
}, [config.projectKey, config.publicKey]);
|
|
40
|
+
return /* @__PURE__ */ jsx(FlagifyContext.Provider, { value: { client, isReady, version }, children });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/useFlag.ts
|
|
44
|
+
import { useContext as useContext2 } from "react";
|
|
45
|
+
|
|
46
|
+
// src/useFlagifyClient.ts
|
|
47
|
+
import { useContext } from "react";
|
|
48
|
+
function useFlagifyClient() {
|
|
49
|
+
const ctx = useContext(FlagifyContext);
|
|
50
|
+
if (ctx === NO_PROVIDER) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"useFlagifyClient must be used within a <FlagifyProvider>. Wrap your app with <FlagifyProvider client={client}>."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return ctx.client;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/useFlag.ts
|
|
59
|
+
function useFlag(flagKey) {
|
|
60
|
+
const { version } = useContext2(FlagifyContext);
|
|
61
|
+
const client = useFlagifyClient();
|
|
62
|
+
void version;
|
|
63
|
+
return client?.isEnabled(flagKey) ?? false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/useFlagValue.ts
|
|
67
|
+
import { useContext as useContext3 } from "react";
|
|
68
|
+
function useFlagValue(flagKey, fallback) {
|
|
69
|
+
const { version } = useContext3(FlagifyContext);
|
|
70
|
+
const client = useFlagifyClient();
|
|
71
|
+
void version;
|
|
72
|
+
return client?.getValue(flagKey, fallback) ?? fallback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/useIsReady.ts
|
|
76
|
+
import { useContext as useContext4 } from "react";
|
|
77
|
+
function useIsReady() {
|
|
78
|
+
const { isReady } = useContext4(FlagifyContext);
|
|
79
|
+
return isReady;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/useVariant.ts
|
|
83
|
+
import { useContext as useContext5 } from "react";
|
|
84
|
+
function useVariant(flagKey, fallback) {
|
|
85
|
+
const { version } = useContext5(FlagifyContext);
|
|
86
|
+
const client = useFlagifyClient();
|
|
87
|
+
void version;
|
|
88
|
+
return client?.getVariant(flagKey, fallback) ?? fallback;
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
FlagifyContext,
|
|
92
|
+
FlagifyProvider,
|
|
93
|
+
NO_PROVIDER,
|
|
94
|
+
useFlag,
|
|
95
|
+
useFlagValue,
|
|
96
|
+
useFlagifyClient,
|
|
97
|
+
useIsReady,
|
|
98
|
+
useVariant
|
|
99
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flagify/react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React SDK for Flagify — hooks and provider for feature flag evaluation.",
|
|
5
|
+
"author": "Mario Campbell R <mario@mariocampbellr.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.mjs",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@flagify/node": "1.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
28
|
+
"@testing-library/react": "^14",
|
|
29
|
+
"@types/react": "^18.2.0",
|
|
30
|
+
"jsdom": "^29.0.1",
|
|
31
|
+
"react": "^18.2.0",
|
|
32
|
+
"react-dom": "18"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"feature-flags",
|
|
36
|
+
"flagify",
|
|
37
|
+
"react",
|
|
38
|
+
"hooks"
|
|
39
|
+
],
|
|
40
|
+
"homepage": "https://flagify.dev",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/flagifyhq/javascript",
|
|
44
|
+
"directory": "packages/react"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/flagifyhq/javascript/issues"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"generate": "barrelsby -c ./barrelsby.json",
|
|
54
|
+
"build": "pnpm run generate && tsup src/index.ts --format esm,cjs --dts",
|
|
55
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"lint": "tsc --noEmit",
|
|
58
|
+
"clean": "rm -rf dist"
|
|
59
|
+
}
|
|
60
|
+
}
|