@ninetailed/experience.js-react 7.11.1 → 7.12.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/index.cjs.js CHANGED
@@ -151,6 +151,83 @@ const usePersonalize = (baseline, variants, options = {
151
151
  return experience_js.selectVariant(baseline, variants, profile, options);
152
152
  };
153
153
 
154
+ /**
155
+ * Custom hook to retrieve a specific feature flag from Ninetailed changes.
156
+ *
157
+ * @param flagKey - The key of the feature flag to retrieve
158
+ * @param defaultValue - The default value to use if the flag is not found
159
+ * @returns An object containing the flag value and status information
160
+ */
161
+ function useFlag(flagKey, defaultValue) {
162
+ const ninetailed = useNinetailed();
163
+ const lastProcessedState = React.useRef(null);
164
+ const [result, setResult] = React.useState({
165
+ value: defaultValue,
166
+ status: 'loading',
167
+ error: null
168
+ });
169
+ React.useEffect(() => {
170
+ // Reset state when dependencies change
171
+ setResult({
172
+ value: defaultValue,
173
+ status: 'loading',
174
+ error: null
175
+ });
176
+ lastProcessedState.current = null;
177
+ const unsubscribe = ninetailed.onChangesChange(changesState => {
178
+ if (lastProcessedState.current && radash.isEqual(lastProcessedState.current, changesState)) {
179
+ experience_jsShared.logger.debug('Change State Did Not Change', changesState);
180
+ return;
181
+ }
182
+ lastProcessedState.current = changesState;
183
+ if (changesState.status === 'loading') {
184
+ // Don't use a function updater here to avoid type issues
185
+ setResult({
186
+ value: defaultValue,
187
+ status: 'loading',
188
+ error: null
189
+ });
190
+ return;
191
+ }
192
+ if (changesState.status === 'error') {
193
+ setResult({
194
+ value: defaultValue,
195
+ status: 'error',
196
+ error: changesState.error
197
+ });
198
+ return;
199
+ }
200
+ try {
201
+ // Find the change with our flag key
202
+ const change = changesState.changes.find(change => change.key === flagKey);
203
+ if (change && change.type === experience_jsShared.ChangeTypes.Variable) {
204
+ const flagValue = change.value;
205
+ setResult({
206
+ value: flagValue,
207
+ status: 'success',
208
+ error: null
209
+ });
210
+ } else {
211
+ // Flag not found or wrong type, use default
212
+ setResult({
213
+ value: defaultValue,
214
+ status: 'success',
215
+ error: null
216
+ });
217
+ }
218
+ } catch (error) {
219
+ setResult({
220
+ value: defaultValue,
221
+ status: 'error',
222
+ error: error instanceof Error ? error : new Error(String(error))
223
+ });
224
+ }
225
+ });
226
+ return unsubscribe;
227
+ }, [ninetailed, flagKey, defaultValue]);
228
+ return result;
229
+ }
230
+
154
231
  const TrackHasSeenComponent = ({
155
232
  children,
156
233
  variant,
@@ -615,6 +692,7 @@ exports.NinetailedProvider = NinetailedProvider;
615
692
  exports.Personalize = Personalize;
616
693
  exports.TrackHasSeenComponent = TrackHasSeenComponent;
617
694
  exports.useExperience = useExperience;
695
+ exports.useFlag = useFlag;
618
696
  exports.useNinetailed = useNinetailed;
619
697
  exports.usePersonalize = usePersonalize;
620
698
  exports.useProfile = useProfile;
package/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { createContext, useMemo, useContext, useState, useRef, useEffect, createElement, forwardRef } from 'react';
2
2
  import { Ninetailed, selectVariant, selectHasExperienceVariants } from '@ninetailed/experience.js';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { logger, isBrowser } from '@ninetailed/experience.js-shared';
4
+ import { logger, ChangeTypes, isBrowser } from '@ninetailed/experience.js-shared';
5
5
  import { isEqual, get } from 'radash';
6
6
  import { isForwardRef } from 'react-is';
7
7
 
@@ -132,6 +132,83 @@ const usePersonalize = (baseline, variants, options = {
132
132
  return selectVariant(baseline, variants, profile, options);
133
133
  };
134
134
 
135
+ /**
136
+ * Custom hook to retrieve a specific feature flag from Ninetailed changes.
137
+ *
138
+ * @param flagKey - The key of the feature flag to retrieve
139
+ * @param defaultValue - The default value to use if the flag is not found
140
+ * @returns An object containing the flag value and status information
141
+ */
142
+ function useFlag(flagKey, defaultValue) {
143
+ const ninetailed = useNinetailed();
144
+ const lastProcessedState = useRef(null);
145
+ const [result, setResult] = useState({
146
+ value: defaultValue,
147
+ status: 'loading',
148
+ error: null
149
+ });
150
+ useEffect(() => {
151
+ // Reset state when dependencies change
152
+ setResult({
153
+ value: defaultValue,
154
+ status: 'loading',
155
+ error: null
156
+ });
157
+ lastProcessedState.current = null;
158
+ const unsubscribe = ninetailed.onChangesChange(changesState => {
159
+ if (lastProcessedState.current && isEqual(lastProcessedState.current, changesState)) {
160
+ logger.debug('Change State Did Not Change', changesState);
161
+ return;
162
+ }
163
+ lastProcessedState.current = changesState;
164
+ if (changesState.status === 'loading') {
165
+ // Don't use a function updater here to avoid type issues
166
+ setResult({
167
+ value: defaultValue,
168
+ status: 'loading',
169
+ error: null
170
+ });
171
+ return;
172
+ }
173
+ if (changesState.status === 'error') {
174
+ setResult({
175
+ value: defaultValue,
176
+ status: 'error',
177
+ error: changesState.error
178
+ });
179
+ return;
180
+ }
181
+ try {
182
+ // Find the change with our flag key
183
+ const change = changesState.changes.find(change => change.key === flagKey);
184
+ if (change && change.type === ChangeTypes.Variable) {
185
+ const flagValue = change.value;
186
+ setResult({
187
+ value: flagValue,
188
+ status: 'success',
189
+ error: null
190
+ });
191
+ } else {
192
+ // Flag not found or wrong type, use default
193
+ setResult({
194
+ value: defaultValue,
195
+ status: 'success',
196
+ error: null
197
+ });
198
+ }
199
+ } catch (error) {
200
+ setResult({
201
+ value: defaultValue,
202
+ status: 'error',
203
+ error: error instanceof Error ? error : new Error(String(error))
204
+ });
205
+ }
206
+ });
207
+ return unsubscribe;
208
+ }, [ninetailed, flagKey, defaultValue]);
209
+ return result;
210
+ }
211
+
135
212
  const TrackHasSeenComponent = ({
136
213
  children,
137
214
  variant,
@@ -590,4 +667,4 @@ const ESRLoadingComponent = _ref => {
590
667
  }));
591
668
  };
592
669
 
593
- export { DefaultExperienceLoadingComponent, ESRLoadingComponent, ESRProvider, Experience, MergeTag, NinetailedProvider, Personalize, TrackHasSeenComponent, useExperience, useNinetailed, usePersonalize, useProfile };
670
+ export { DefaultExperienceLoadingComponent, ESRLoadingComponent, ESRProvider, Experience, MergeTag, NinetailedProvider, Personalize, TrackHasSeenComponent, useExperience, useFlag, useNinetailed, usePersonalize, useProfile };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@ninetailed/experience.js-react",
3
- "version": "7.11.1",
3
+ "version": "7.12.0",
4
4
  "description": "Ninetailed SDK for React",
5
5
  "dependencies": {
6
- "@ninetailed/experience.js": "7.11.1",
7
- "@ninetailed/experience.js-shared": "7.11.1",
8
- "@ninetailed/experience.js-plugin-analytics": "7.11.1",
6
+ "@ninetailed/experience.js": "7.12.0",
7
+ "@ninetailed/experience.js-shared": "7.12.0",
8
+ "@ninetailed/experience.js-plugin-analytics": "7.12.0",
9
9
  "radash": "10.9.0",
10
10
  "react-is": "18.2.0"
11
11
  },
@@ -5,6 +5,7 @@ export type { NinetailedProviderProps, NinetailedProviderInstantiationProps, } f
5
5
  export { useNinetailed } from './useNinetailed';
6
6
  export { useProfile } from './useProfile';
7
7
  export { usePersonalize } from './usePersonalize';
8
+ export { useFlag } from './useFlag';
8
9
  export { Personalize } from './Personalize';
9
10
  export type { PersonalizedComponent } from './Personalize';
10
11
  export { MergeTag } from './MergeTag';
@@ -0,0 +1,22 @@
1
+ import { AllowedVariableType } from '@ninetailed/experience.js-shared';
2
+ export type FlagResult<T> = {
3
+ status: 'loading';
4
+ value: T;
5
+ error: null;
6
+ } | {
7
+ status: 'success';
8
+ value: T;
9
+ error: null;
10
+ } | {
11
+ status: 'error';
12
+ value: T;
13
+ error: Error;
14
+ };
15
+ /**
16
+ * Custom hook to retrieve a specific feature flag from Ninetailed changes.
17
+ *
18
+ * @param flagKey - The key of the feature flag to retrieve
19
+ * @param defaultValue - The default value to use if the flag is not found
20
+ * @returns An object containing the flag value and status information
21
+ */
22
+ export declare function useFlag<T extends AllowedVariableType>(flagKey: string, defaultValue: T): FlagResult<T>;