@ninetailed/experience.js-react 7.15.1 → 7.17.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 +56 -40
- package/index.esm.js +59 -42
- package/package.json +4 -4
- package/src/lib/index.d.ts +1 -0
- package/src/lib/useFlag.d.ts +5 -2
- package/src/lib/useFlagWithManualTracking.d.ts +19 -0
package/index.cjs.js
CHANGED
|
@@ -153,32 +153,34 @@ const usePersonalize = (baseline, variants, options = {
|
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
* Hook to access a Ninetailed variable flag with
|
|
156
|
+
* Hook to access a Ninetailed variable flag with manual tracking control.
|
|
157
157
|
*/
|
|
158
|
-
function
|
|
158
|
+
function useFlagWithManualTracking(flagKey, defaultValue) {
|
|
159
159
|
const ninetailed = useNinetailed();
|
|
160
|
-
const lastProcessedState = React.useRef(null);
|
|
161
|
-
const defaultValueRef = React.useRef(defaultValue);
|
|
162
160
|
const flagKeyRef = React.useRef(flagKey);
|
|
161
|
+
const defaultValueRef = React.useRef(defaultValue);
|
|
162
|
+
const lastProcessedState = React.useRef(null);
|
|
163
|
+
const changeRef = React.useRef(null);
|
|
163
164
|
const [result, setResult] = React.useState({
|
|
164
165
|
value: defaultValue,
|
|
165
166
|
status: 'loading',
|
|
166
167
|
error: null
|
|
167
168
|
});
|
|
168
|
-
// Reset
|
|
169
|
+
// Reset if inputs change
|
|
169
170
|
React.useEffect(() => {
|
|
170
171
|
if (!radash.isEqual(defaultValueRef.current, defaultValue) || flagKeyRef.current !== flagKey) {
|
|
171
172
|
defaultValueRef.current = defaultValue;
|
|
172
173
|
flagKeyRef.current = flagKey;
|
|
174
|
+
lastProcessedState.current = null;
|
|
175
|
+
changeRef.current = null;
|
|
173
176
|
setResult({
|
|
174
177
|
value: defaultValue,
|
|
175
178
|
status: 'loading',
|
|
176
179
|
error: null
|
|
177
180
|
});
|
|
178
|
-
lastProcessedState.current = null;
|
|
179
181
|
}
|
|
180
182
|
}, [flagKey, defaultValue]);
|
|
181
|
-
//
|
|
183
|
+
// Listen for personalization state changes
|
|
182
184
|
React.useEffect(() => {
|
|
183
185
|
const unsubscribe = ninetailed.onChangesChange(changesState => {
|
|
184
186
|
if (lastProcessedState.current && radash.isEqual(lastProcessedState.current, changesState)) {
|
|
@@ -201,46 +203,59 @@ function useFlag(flagKey, defaultValue, options = {}) {
|
|
|
201
203
|
});
|
|
202
204
|
return;
|
|
203
205
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
ninetailed.trackVariableComponentView({
|
|
218
|
-
variable: change.value,
|
|
219
|
-
variant: {
|
|
220
|
-
id: `Variable-${key}`
|
|
221
|
-
},
|
|
222
|
-
componentType: 'Variable',
|
|
223
|
-
variantIndex: change.meta.variantIndex,
|
|
224
|
-
experienceId: change.meta.experienceId
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
setResult({
|
|
229
|
-
value: defaultValueRef.current,
|
|
230
|
-
status: 'success',
|
|
231
|
-
error: null
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
} catch (error) {
|
|
206
|
+
// Find relevant change for this flag
|
|
207
|
+
const change = changesState.changes.find(c => c.key === flagKeyRef.current && c.type === experience_jsShared.ChangeTypes.Variable);
|
|
208
|
+
if (change) {
|
|
209
|
+
changeRef.current = change;
|
|
210
|
+
const rawValue = change.value;
|
|
211
|
+
const actualValue = rawValue && typeof rawValue === 'object' && rawValue !== null && 'value' in rawValue && typeof rawValue['value'] === 'object' ? rawValue['value'] : rawValue;
|
|
212
|
+
setResult({
|
|
213
|
+
value: actualValue,
|
|
214
|
+
status: 'success',
|
|
215
|
+
error: null
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
changeRef.current = null;
|
|
235
219
|
setResult({
|
|
236
220
|
value: defaultValueRef.current,
|
|
237
|
-
status: '
|
|
238
|
-
error:
|
|
221
|
+
status: 'success',
|
|
222
|
+
error: null
|
|
239
223
|
});
|
|
240
224
|
}
|
|
241
225
|
});
|
|
242
226
|
return unsubscribe;
|
|
243
227
|
}, [ninetailed]);
|
|
228
|
+
// Manual tracking function
|
|
229
|
+
const track = React.useCallback(() => {
|
|
230
|
+
const change = changeRef.current;
|
|
231
|
+
if (!change) return;
|
|
232
|
+
ninetailed.trackVariableComponentView({
|
|
233
|
+
variable: change.value,
|
|
234
|
+
variant: {
|
|
235
|
+
id: `Variable-${flagKeyRef.current}`
|
|
236
|
+
},
|
|
237
|
+
componentType: 'Variable',
|
|
238
|
+
variantIndex: change.meta.variantIndex,
|
|
239
|
+
experienceId: change.meta.experienceId
|
|
240
|
+
});
|
|
241
|
+
}, [ninetailed]);
|
|
242
|
+
return [result, track];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Hook to access a Ninetailed variable flag with built-in auto-tracking.
|
|
247
|
+
*
|
|
248
|
+
* @remarks
|
|
249
|
+
* For manual control over tracking behavior, consider using {@link useFlagWithManualTracking}.
|
|
250
|
+
*/
|
|
251
|
+
function useFlag(flagKey, defaultValue, options = {}) {
|
|
252
|
+
const [result, track] = useFlagWithManualTracking(flagKey, defaultValue);
|
|
253
|
+
React.useEffect(() => {
|
|
254
|
+
const shouldAutoTrack = typeof options.shouldAutoTrack === 'function' ? options.shouldAutoTrack() : options.shouldAutoTrack !== false;
|
|
255
|
+
if (result.status === 'success' && shouldAutoTrack) {
|
|
256
|
+
track();
|
|
257
|
+
}
|
|
258
|
+
}, [result.status, track, options.shouldAutoTrack]);
|
|
244
259
|
return result;
|
|
245
260
|
}
|
|
246
261
|
|
|
@@ -720,6 +735,7 @@ exports.Personalize = Personalize;
|
|
|
720
735
|
exports.TrackHasSeenComponent = TrackHasSeenComponent;
|
|
721
736
|
exports.useExperience = useExperience;
|
|
722
737
|
exports.useFlag = useFlag;
|
|
738
|
+
exports.useFlagWithManualTracking = useFlagWithManualTracking;
|
|
723
739
|
exports.useNinetailed = useNinetailed;
|
|
724
740
|
exports.usePersonalize = usePersonalize;
|
|
725
741
|
exports.useProfile = useProfile;
|
package/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext, useMemo, useContext, useState, useRef, useEffect, createElement, forwardRef } from 'react';
|
|
1
|
+
import React, { createContext, useMemo, useContext, useState, useRef, useEffect, useCallback, 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
4
|
import { logger, ChangeTypes, isBrowser, circularJsonStringify } from '@ninetailed/experience.js-shared';
|
|
@@ -134,34 +134,36 @@ const usePersonalize = (baseline, variants, options = {
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
* Hook to access a Ninetailed variable flag with
|
|
137
|
+
* Hook to access a Ninetailed variable flag with manual tracking control.
|
|
138
138
|
*/
|
|
139
|
-
function
|
|
139
|
+
function useFlagWithManualTracking(flagKey, defaultValue) {
|
|
140
140
|
const ninetailed = useNinetailed();
|
|
141
|
-
const lastProcessedState = useRef(null);
|
|
142
|
-
const defaultValueRef = useRef(defaultValue);
|
|
143
141
|
const flagKeyRef = useRef(flagKey);
|
|
142
|
+
const defaultValueRef = useRef(defaultValue);
|
|
143
|
+
const lastProcessedState = useRef(null);
|
|
144
|
+
const changeRef = useRef(null);
|
|
144
145
|
const [result, setResult] = useState({
|
|
145
146
|
value: defaultValue,
|
|
146
147
|
status: 'loading',
|
|
147
148
|
error: null
|
|
148
149
|
});
|
|
149
150
|
|
|
150
|
-
// Reset
|
|
151
|
+
// Reset if inputs change
|
|
151
152
|
useEffect(() => {
|
|
152
153
|
if (!isEqual(defaultValueRef.current, defaultValue) || flagKeyRef.current !== flagKey) {
|
|
153
154
|
defaultValueRef.current = defaultValue;
|
|
154
155
|
flagKeyRef.current = flagKey;
|
|
156
|
+
lastProcessedState.current = null;
|
|
157
|
+
changeRef.current = null;
|
|
155
158
|
setResult({
|
|
156
159
|
value: defaultValue,
|
|
157
160
|
status: 'loading',
|
|
158
161
|
error: null
|
|
159
162
|
});
|
|
160
|
-
lastProcessedState.current = null;
|
|
161
163
|
}
|
|
162
164
|
}, [flagKey, defaultValue]);
|
|
163
165
|
|
|
164
|
-
//
|
|
166
|
+
// Listen for personalization state changes
|
|
165
167
|
useEffect(() => {
|
|
166
168
|
const unsubscribe = ninetailed.onChangesChange(changesState => {
|
|
167
169
|
if (lastProcessedState.current && isEqual(lastProcessedState.current, changesState)) {
|
|
@@ -184,46 +186,61 @@ function useFlag(flagKey, defaultValue, options = {}) {
|
|
|
184
186
|
});
|
|
185
187
|
return;
|
|
186
188
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
variable: change.value,
|
|
202
|
-
variant: {
|
|
203
|
-
id: `Variable-${key}`
|
|
204
|
-
},
|
|
205
|
-
componentType: 'Variable',
|
|
206
|
-
variantIndex: change.meta.variantIndex,
|
|
207
|
-
experienceId: change.meta.experienceId
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
setResult({
|
|
212
|
-
value: defaultValueRef.current,
|
|
213
|
-
status: 'success',
|
|
214
|
-
error: null
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
189
|
+
|
|
190
|
+
// Find relevant change for this flag
|
|
191
|
+
const change = changesState.changes.find(c => c.key === flagKeyRef.current && c.type === ChangeTypes.Variable);
|
|
192
|
+
if (change) {
|
|
193
|
+
changeRef.current = change;
|
|
194
|
+
const rawValue = change.value;
|
|
195
|
+
const actualValue = rawValue && typeof rawValue === 'object' && rawValue !== null && 'value' in rawValue && typeof rawValue['value'] === 'object' ? rawValue['value'] : rawValue;
|
|
196
|
+
setResult({
|
|
197
|
+
value: actualValue,
|
|
198
|
+
status: 'success',
|
|
199
|
+
error: null
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
changeRef.current = null;
|
|
218
203
|
setResult({
|
|
219
204
|
value: defaultValueRef.current,
|
|
220
|
-
status: '
|
|
221
|
-
error:
|
|
205
|
+
status: 'success',
|
|
206
|
+
error: null
|
|
222
207
|
});
|
|
223
208
|
}
|
|
224
209
|
});
|
|
225
210
|
return unsubscribe;
|
|
226
211
|
}, [ninetailed]);
|
|
212
|
+
|
|
213
|
+
// Manual tracking function
|
|
214
|
+
const track = useCallback(() => {
|
|
215
|
+
const change = changeRef.current;
|
|
216
|
+
if (!change) return;
|
|
217
|
+
ninetailed.trackVariableComponentView({
|
|
218
|
+
variable: change.value,
|
|
219
|
+
variant: {
|
|
220
|
+
id: `Variable-${flagKeyRef.current}`
|
|
221
|
+
},
|
|
222
|
+
componentType: 'Variable',
|
|
223
|
+
variantIndex: change.meta.variantIndex,
|
|
224
|
+
experienceId: change.meta.experienceId
|
|
225
|
+
});
|
|
226
|
+
}, [ninetailed]);
|
|
227
|
+
return [result, track];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Hook to access a Ninetailed variable flag with built-in auto-tracking.
|
|
232
|
+
*
|
|
233
|
+
* @remarks
|
|
234
|
+
* For manual control over tracking behavior, consider using {@link useFlagWithManualTracking}.
|
|
235
|
+
*/
|
|
236
|
+
function useFlag(flagKey, defaultValue, options = {}) {
|
|
237
|
+
const [result, track] = useFlagWithManualTracking(flagKey, defaultValue);
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
const shouldAutoTrack = typeof options.shouldAutoTrack === 'function' ? options.shouldAutoTrack() : options.shouldAutoTrack !== false;
|
|
240
|
+
if (result.status === 'success' && shouldAutoTrack) {
|
|
241
|
+
track();
|
|
242
|
+
}
|
|
243
|
+
}, [result.status, track, options.shouldAutoTrack]);
|
|
227
244
|
return result;
|
|
228
245
|
}
|
|
229
246
|
|
|
@@ -696,4 +713,4 @@ const ESRLoadingComponent = _ref => {
|
|
|
696
713
|
}));
|
|
697
714
|
};
|
|
698
715
|
|
|
699
|
-
export { DefaultExperienceLoadingComponent, ESRLoadingComponent, ESRProvider, Experience, MergeTag, NinetailedProvider, Personalize, TrackHasSeenComponent, useExperience, useFlag, useNinetailed, usePersonalize, useProfile };
|
|
716
|
+
export { DefaultExperienceLoadingComponent, ESRLoadingComponent, ESRProvider, Experience, MergeTag, NinetailedProvider, Personalize, TrackHasSeenComponent, useExperience, useFlag, useFlagWithManualTracking, useNinetailed, usePersonalize, useProfile };
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninetailed/experience.js-react",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.17.0",
|
|
4
4
|
"description": "Ninetailed SDK for React",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@ninetailed/experience.js": "7.
|
|
7
|
-
"@ninetailed/experience.js-shared": "7.
|
|
8
|
-
"@ninetailed/experience.js-plugin-analytics": "7.
|
|
6
|
+
"@ninetailed/experience.js": "7.17.0",
|
|
7
|
+
"@ninetailed/experience.js-shared": "7.17.0",
|
|
8
|
+
"@ninetailed/experience.js-plugin-analytics": "7.17.0",
|
|
9
9
|
"radash": "10.9.0",
|
|
10
10
|
"react-is": "18.2.0"
|
|
11
11
|
},
|
package/src/lib/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { useNinetailed } from './useNinetailed';
|
|
|
6
6
|
export { useProfile } from './useProfile';
|
|
7
7
|
export { usePersonalize } from './usePersonalize';
|
|
8
8
|
export { useFlag } from './useFlag';
|
|
9
|
+
export { useFlagWithManualTracking } from './useFlagWithManualTracking';
|
|
9
10
|
export { Personalize } from './Personalize';
|
|
10
11
|
export type { PersonalizedComponent } from './Personalize';
|
|
11
12
|
export { MergeTag } from './MergeTag';
|
package/src/lib/useFlag.d.ts
CHANGED
|
@@ -13,10 +13,13 @@ export type FlagResult<T> = {
|
|
|
13
13
|
error: Error;
|
|
14
14
|
};
|
|
15
15
|
type UseFlagOptions = {
|
|
16
|
-
|
|
16
|
+
shouldAutoTrack?: boolean | (() => boolean);
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
|
-
* Hook to access a Ninetailed variable flag with built-in tracking.
|
|
19
|
+
* Hook to access a Ninetailed variable flag with built-in auto-tracking.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* For manual control over tracking behavior, consider using {@link useFlagWithManualTracking}.
|
|
20
23
|
*/
|
|
21
24
|
export declare function useFlag<T extends AllowedVariableType>(flagKey: string, defaultValue: T, options?: UseFlagOptions): FlagResult<T>;
|
|
22
25
|
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
export type FlagResultWithTracking<T> = [FlagResult<T>, () => void];
|
|
16
|
+
/**
|
|
17
|
+
* Hook to access a Ninetailed variable flag with manual tracking control.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useFlagWithManualTracking<T extends AllowedVariableType>(flagKey: string, defaultValue: T): FlagResultWithTracking<T>;
|