Merge branch 'INDA-330-pii-update' into 'main'
[ProtonMail-WebClient.git] / packages / features / useFeatures.ts
blob2535b52e389114cdf2d33cb6bde5eaea0d613a02
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>>;
17     code: FeatureCode;
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;
26         codeQueue = [];
27         return dispatch(fetchFeatures(unique(codes.concat(oldCodeQueue))));
28     }, []);
30     const put = async (code: FeatureCode, value: any) => {
31         return dispatch(updateFeature(code, value));
32     };
34     const enqueue = (codes: FeatureCode[]) => {
35         codeQueue = unique(codeQueue.concat(codes));
36     };
38     useMemo(() => {
39         // Features are queued up during render to gather possible feature codes from
40         // children and merge them together with feature codes from parents
41         enqueue(codes);
42     }, codes);
44     useEffect(() => {
45         if (prefetch && codes.some((code) => !isValidFeature(state[code]))) {
46             get(codes).catch(noop);
47         }
48     }, codes);
50     const featuresFlags = codes.map((code): FeatureContextValue => {
51         const featureStateValue = state[code]?.value;
52         return {
53             get: () => get([code]).then((featuresState) => featuresState[code]),
54             update: (value) => put(code, value),
55             feature: featureStateValue,
56             loading: featureStateValue === undefined,
57             code,
58         };
59     });
61     type CodeType = (typeof codes)[number];
62     const getFeature = <T extends CodeType>(code: T) => {
63         const feature = featuresFlags.find((feature) => feature.code === code);
64         if (!feature) {
65             throw new Error('Feature not present in codes');
66         }
68         return feature;
69     };
71     return { featuresFlags, getFeature };
74 export default useFeatures;