1 import { useCallback, useEffect, useMemo } from 'react';
3 import { useDispatch, useSelector } from '@proton/redux-shared-store';
4 import noop from '@proton/utils/noop';
5 import unique from '@proton/utils/unique';
7 import type { Feature, FeatureCode } from './interface';
8 import { fetchFeatures, isValidFeature, selectFeatures, updateFeature } from './reducer';
10 let codeQueue: FeatureCode[] = [];
12 export interface FeatureContextValue<V = any> {
13 feature: Feature<V> | undefined;
14 loading: boolean | undefined;
15 get: () => Promise<Feature<V>>;
16 update: (value: V) => Promise<Feature<V>>;
20 const useFeatures = <Flags extends FeatureCode>(codes: Flags[], prefetch = true) => {
21 const dispatch = useDispatch();
22 const state = useSelector(selectFeatures);
24 const get = useCallback((codes: FeatureCode[]) => {
25 const oldCodeQueue = codeQueue;
27 return dispatch(fetchFeatures(unique(codes.concat(oldCodeQueue))));
30 const put = async (code: FeatureCode, value: any) => {
31 return dispatch(updateFeature(code, value));
34 const enqueue = (codes: FeatureCode[]) => {
35 codeQueue = unique(codeQueue.concat(codes));
39 // Features are queued up during render to gather possible feature codes from
40 // children and merge them together with feature codes from parents
45 if (prefetch && codes.some((code) => !isValidFeature(state[code]))) {
46 get(codes).catch(noop);
50 const featuresFlags = codes.map((code): FeatureContextValue => {
51 const featureStateValue = state[code]?.value;
53 get: () => get([code]).then((featuresState) => featuresState[code]),
54 update: (value) => put(code, value),
55 feature: featureStateValue,
56 loading: featureStateValue === undefined,
61 type CodeType = (typeof codes)[number];
62 const getFeature = <T extends CodeType>(code: T) => {
63 const feature = featuresFlags.find((feature) => feature.code === code);
65 throw new Error('Feature not present in codes');
71 return { featuresFlags, getFeature };
74 export default useFeatures;