@ttoss/react-feature-flags 0.2.11 → 0.2.13
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 +122 -5
- package/dist/esm/index.js +8 -3
- package/dist/index.d.ts +2 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -64,14 +64,25 @@ const MyComponent = () => {
|
|
|
64
64
|
|
|
65
65
|
### `FeatureFlag` component
|
|
66
66
|
|
|
67
|
-
You can use `FeatureFlag` component to render its children only if the feature flag is enabled. It has
|
|
67
|
+
You can use `FeatureFlag` component to render its children only if the feature flag is enabled. It has optional props for error handling and fallback content.
|
|
68
|
+
|
|
69
|
+
**Props:**
|
|
70
|
+
|
|
71
|
+
- `name`: Feature flag name
|
|
72
|
+
- `children`: Component to render when feature is enabled
|
|
73
|
+
- `fallback`: Component to render when feature is disabled (optional)
|
|
74
|
+
- `errorFallback`: Component to render when feature is enabled but an error occurs (optional)
|
|
68
75
|
|
|
69
76
|
```tsx
|
|
70
77
|
import { FeatureFlag } from '@ttoss/react-feature-flags';
|
|
71
78
|
|
|
72
79
|
const MyComponent = () => {
|
|
73
80
|
return (
|
|
74
|
-
<FeatureFlag
|
|
81
|
+
<FeatureFlag
|
|
82
|
+
name="my-feature"
|
|
83
|
+
fallback={<div>Feature is disabled</div>}
|
|
84
|
+
errorFallback={<div>Something went wrong</div>}
|
|
85
|
+
>
|
|
75
86
|
<div>Feature is enabled</div>
|
|
76
87
|
</FeatureFlag>
|
|
77
88
|
);
|
|
@@ -80,13 +91,13 @@ const MyComponent = () => {
|
|
|
80
91
|
|
|
81
92
|
### Update feature flags
|
|
82
93
|
|
|
83
|
-
You can update feature flags by calling `updateFeatures` function that is returned from `
|
|
94
|
+
You can update feature flags by calling `updateFeatures` function that is returned from `useUpdateFeatures` hook. This is useful when you want to update feature flags after providers are initialized.
|
|
84
95
|
|
|
85
96
|
```tsx
|
|
86
|
-
import {
|
|
97
|
+
import { useUpdateFeatures } from '@ttoss/react-feature-flags';
|
|
87
98
|
|
|
88
99
|
const MyComponent = () => {
|
|
89
|
-
const { updateFeatures } =
|
|
100
|
+
const { updateFeatures } = useUpdateFeatures();
|
|
90
101
|
const handleClick = async () => {
|
|
91
102
|
const response = await fetch('https://...');
|
|
92
103
|
const { features } = await response.json();
|
|
@@ -96,6 +107,31 @@ const MyComponent = () => {
|
|
|
96
107
|
};
|
|
97
108
|
```
|
|
98
109
|
|
|
110
|
+
### Error Handling
|
|
111
|
+
|
|
112
|
+
The `FeatureFlag` component includes built-in error boundary protection. When a feature is enabled but the wrapped component throws an error, it will render the `errorFallback` instead of crashing the entire application.
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { FeatureFlag } from '@ttoss/react-feature-flags';
|
|
116
|
+
|
|
117
|
+
const MyComponent = () => {
|
|
118
|
+
return (
|
|
119
|
+
<FeatureFlag
|
|
120
|
+
name="experimental-feature"
|
|
121
|
+
errorFallback={<div>This feature is temporarily unavailable</div>}
|
|
122
|
+
>
|
|
123
|
+
<ExperimentalComponent />
|
|
124
|
+
</FeatureFlag>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This is especially useful for:
|
|
130
|
+
|
|
131
|
+
- **Experimental features** that might have bugs
|
|
132
|
+
- **Gradual rollouts** where you want graceful degradation
|
|
133
|
+
- **Production safety** when testing new functionality
|
|
134
|
+
|
|
99
135
|
### TypeScript
|
|
100
136
|
|
|
101
137
|
If you are using TypeScript, you can define your feature flags names on `feature-flags.d.ts` file.
|
|
@@ -118,3 +154,84 @@ const MyComponent = () => {
|
|
|
118
154
|
return <div>{isFeatureEnabled ? 'Enabled' : 'Disabled'}</div>;
|
|
119
155
|
};
|
|
120
156
|
```
|
|
157
|
+
|
|
158
|
+
## Examples
|
|
159
|
+
|
|
160
|
+
### `loadFeatures` function needs a hook
|
|
161
|
+
|
|
162
|
+
If `loadFeatures` function needs to use data from a hook, you can create a custom Provider that uses the hook, passes the data to `loadFeatures` function, and then wraps the `FeatureFlagsProvider`.
|
|
163
|
+
|
|
164
|
+
For example, you need `userId` from a custom hook `useMe` to load features:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import * as React from 'react';
|
|
168
|
+
import { FeatureFlagsProvider as TtossFeatureFlagsProvider } from '@ttoss/react-feature-flags';
|
|
169
|
+
|
|
170
|
+
const FeatureFlagsProvider = ({ children }: { children: React.ReactNode }) => {
|
|
171
|
+
const { me } = useMe();
|
|
172
|
+
|
|
173
|
+
const loadFeatures = React.useCallback(async () => {
|
|
174
|
+
if (!me?.email) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Specify modules that some users have access to.
|
|
180
|
+
*/
|
|
181
|
+
if (me.email === 'user@example.com') {
|
|
182
|
+
return ['module1', 'module2'];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return [];
|
|
186
|
+
}, [me?.email]);
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<TtossFeatureFlagsProvider loadFeatures={loadFeatures}>
|
|
190
|
+
{children}
|
|
191
|
+
</TtossFeatureFlagsProvider>
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Best Practices
|
|
197
|
+
|
|
198
|
+
### Use Unique Entrypoints
|
|
199
|
+
|
|
200
|
+
When implementing feature flags, always ensure that **all dependencies** for your new feature are contained within the feature flag boundary. This prevents failures when the feature is disabled.
|
|
201
|
+
|
|
202
|
+
#### ✅ Recommended: Unique Entrypoint
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { FeatureFlag } from '@ttoss/react-feature-flags';
|
|
206
|
+
import { MyNewComponent } from './MyNewComponent';
|
|
207
|
+
|
|
208
|
+
const MyComponent = () => {
|
|
209
|
+
return (
|
|
210
|
+
<FeatureFlag name="my-feature" fallback={null}>
|
|
211
|
+
<MyNewComponent />
|
|
212
|
+
</FeatureFlag>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### ❌ Avoid: Non-Unique Entrypoint
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { FeatureFlag } from '@ttoss/react-feature-flags';
|
|
221
|
+
import { MyNewComponent } from './MyNewComponent';
|
|
222
|
+
import { useMyNewComponentHook } from './useMyNewComponentHook';
|
|
223
|
+
|
|
224
|
+
const MyComponent = () => {
|
|
225
|
+
const data = useMyNewComponentHook(); // This executes even when feature is disabled
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<FeatureFlag name="my-feature" fallback={null}>
|
|
229
|
+
<MyNewComponent data={data} />
|
|
230
|
+
</FeatureFlag>
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Why this matters**: In the non-unique entrypoint example, `useMyNewComponentHook()` executes regardless of whether the feature flag is enabled. If this hook fails or has dependencies that don't exist when the feature is disabled, it will break the entire `MyComponent`, even though the feature flag should prevent this.
|
|
236
|
+
|
|
237
|
+
**Solution**: Move all feature-related logic, including hooks, API calls, and dependencies, inside the component that's wrapped by the feature flag.
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
2
|
|
|
3
|
+
// src/FeatureFlag.tsx
|
|
4
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
5
|
+
|
|
3
6
|
// src/useFeatureFlag.ts
|
|
4
7
|
import * as React2 from "react";
|
|
5
8
|
|
|
@@ -38,17 +41,19 @@ var useFeatureFlag = name => {
|
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
// src/FeatureFlag.tsx
|
|
41
|
-
import {
|
|
44
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
42
45
|
var FeatureFlag = ({
|
|
43
46
|
name,
|
|
44
47
|
children,
|
|
45
|
-
fallback = null
|
|
48
|
+
fallback = null,
|
|
49
|
+
errorFallback = null
|
|
46
50
|
}) => {
|
|
47
51
|
const isEnabled = useFeatureFlag(name);
|
|
48
52
|
if (!isEnabled) {
|
|
49
53
|
return fallback;
|
|
50
54
|
}
|
|
51
|
-
return /* @__PURE__ */jsx2(
|
|
55
|
+
return /* @__PURE__ */jsx2(ErrorBoundary, {
|
|
56
|
+
fallback: errorFallback,
|
|
52
57
|
children
|
|
53
58
|
});
|
|
54
59
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,9 @@ interface FeatureFlagProps {
|
|
|
5
5
|
name: FeatureFlags;
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
fallback?: React.ReactNode;
|
|
8
|
+
errorFallback?: React.ReactNode;
|
|
8
9
|
}
|
|
9
|
-
declare const FeatureFlag: ({ name, children, fallback, }: FeatureFlagProps) => string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null;
|
|
10
|
+
declare const FeatureFlag: ({ name, children, fallback, errorFallback, }: FeatureFlagProps) => string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null;
|
|
10
11
|
|
|
11
12
|
declare const FeatureFlagsProvider: ({ children, loadFeatures, }: {
|
|
12
13
|
children: React.ReactNode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/react-feature-flags",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "React Feature Flags",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ttoss",
|
|
@@ -22,16 +22,19 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"react-error-boundary": "^5.0.0"
|
|
27
|
+
},
|
|
25
28
|
"peerDependencies": {
|
|
26
29
|
"react": ">=16.8.0"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@types/react": "^19.1.8",
|
|
30
|
-
"jest": "^30.0.
|
|
33
|
+
"jest": "^30.0.4",
|
|
31
34
|
"react": "^19.1.0",
|
|
32
35
|
"tsup": "^8.5.0",
|
|
33
|
-
"@ttoss/config": "^1.35.
|
|
34
|
-
"@ttoss/test-utils": "^2.1.
|
|
36
|
+
"@ttoss/config": "^1.35.5",
|
|
37
|
+
"@ttoss/test-utils": "^2.1.25"
|
|
35
38
|
},
|
|
36
39
|
"keywords": [
|
|
37
40
|
"feature-flags",
|