@ttoss/react-feature-flags 0.2.11 → 0.2.12

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
@@ -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 a `fallback` (optional) prop that can be used to render something else if the feature flag is disabled.
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 name="my-feature" fallback={<div>Feature is disabled</div>}>
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 `useFeatureFlags` hook. This is useful when you want to update feature flags after providers are initialized.
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 { useFeatureFlags } from '@ttoss/react-feature-flags';
97
+ import { useUpdateFeatures } from '@ttoss/react-feature-flags';
87
98
 
88
99
  const MyComponent = () => {
89
- const { updateFeatures } = useFeatureFlags();
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 { Fragment, jsx as jsx2 } from "react/jsx-runtime";
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(Fragment, {
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.11",
3
+ "version": "0.2.12",
4
4
  "description": "React Feature Flags",
5
5
  "license": "MIT",
6
6
  "author": "ttoss",
@@ -22,6 +22,9 @@
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
  },
@@ -30,8 +33,8 @@
30
33
  "jest": "^30.0.2",
31
34
  "react": "^19.1.0",
32
35
  "tsup": "^8.5.0",
33
- "@ttoss/config": "^1.35.4",
34
- "@ttoss/test-utils": "^2.1.24"
36
+ "@ttoss/test-utils": "^2.1.24",
37
+ "@ttoss/config": "^1.35.4"
35
38
  },
36
39
  "keywords": [
37
40
  "feature-flags",