-
Notifications
You must be signed in to change notification settings - Fork 0
/
react.ts
114 lines (102 loc) · 3.02 KB
/
react.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"use client";
// @deno-types="npm:@types/react"
import {
createContext,
createElement,
type ReactNode,
useContext,
useEffect,
useSyncExternalStore,
} from "npm:react";
import {
type AnyFeatureFlags,
FeatureFlags,
type FeatureFlagSchema,
type Overrides,
type TFeatureFlags,
} from "./mod.ts";
type State<T extends AnyFeatureFlags> = [
TFeatureFlags<T["schema"]>,
(updates: Partial<TFeatureFlags<T["schema"]>>) => void,
];
type ContextContent<T extends AnyFeatureFlags> = {
featureFlags: T;
state: State<T>;
};
export const FeatureFlagContext = createContext<
ContextContent<AnyFeatureFlags>
>({
featureFlags: new FeatureFlags({ schema: {} }),
state: [{}, () => {}],
});
export function FeatureFlagProvider<
Environment extends string,
Schema extends FeatureFlagSchema,
>({ children, hydratedOverrides, ...options }: {
featureFlags: FeatureFlags<Environment, Schema>;
options?: never;
hydratedOverrides?: Overrides<Schema>;
children: ReactNode;
} | {
featureFlags?: never;
options: ConstructorParameters<typeof FeatureFlags<Environment, Schema>>[0];
hydratedOverrides?: Overrides<Schema>;
children: ReactNode;
}) {
const featureFlags = options.featureFlags ||
new FeatureFlags<Environment, Schema>(options.options);
const jsonState = useSyncExternalStore<string>(
(listener) => {
featureFlags.subscribe(listener);
return () => featureFlags.unsubscribe(listener);
},
() => JSON.stringify(featureFlags.store),
() => JSON.stringify(featureFlags.initialStore),
);
const state = JSON.parse(jsonState) as TFeatureFlags<
typeof featureFlags["schema"]
>;
function setState(
updates: Partial<TFeatureFlags<typeof featureFlags["schema"]>>,
) {
const flags = Object.keys(updates) as (keyof typeof updates)[];
flags.forEach((flag) => {
const v = updates[flag];
if (FeatureFlags.isValidValue(v)) {
featureFlags.set(flag, v);
}
});
}
useEffect(() => {
if (!hydratedOverrides) return;
const flags = FeatureFlags.listOptionsFromSchema(featureFlags.schema);
flags.forEach((flag) => {
const override = hydratedOverrides(flag);
if (FeatureFlags.isValidValue(override)) {
try {
featureFlags.set(flag, override);
} catch (_e) {
/* ignore readonly errors here */
}
}
});
}, []);
return createElement(
FeatureFlagContext.Provider,
{
value: {
featureFlags,
state: [state, setState] as State<typeof featureFlags>,
},
},
children,
);
}
export function featureFlagsHookFactory<T extends AnyFeatureFlags>(
_: T,
) {
return () => {
const { state } = useContext(FeatureFlagContext) as ContextContent<T>;
return state;
};
}