Skip to content

Commit 7d8608d

Browse files
committed
Introduce react hooks
1 parent f701ac5 commit 7d8608d

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

compile.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import project from "./project.json" assert { type: "json" };
55
await emptyDir("./npm");
66

77
await build({
8-
entryPoints: ["./mod.ts"],
8+
entryPoints: ["./mod.ts", {
9+
name: "./react",
10+
path: "react.ts",
11+
}],
912
outDir: "./npm",
1013
shims: {
1114
// see JS docs for overview and more options

react.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use client";
2+
3+
// @deno-types="npm:@types/react"
4+
import {
5+
createContext,
6+
ReactNode,
7+
useContext,
8+
useEffect,
9+
useSyncExternalStore,
10+
} from "npm:react";
11+
12+
import {
13+
FeatureFlags,
14+
FeatureFlagSchema,
15+
Overrides,
16+
TFeatureFlags,
17+
} from "./mod.ts";
18+
19+
type State<
20+
Environment extends string,
21+
Schema extends FeatureFlagSchema,
22+
> = {
23+
featureFlags: FeatureFlags<Environment, Schema>;
24+
state: TFeatureFlags<Schema>;
25+
};
26+
27+
// deno-lint-ignore no-explicit-any
28+
export const FeatureFlagContext = createContext<State<any, any>>({
29+
featureFlags: new FeatureFlags({ schema: {} }),
30+
state: {},
31+
});
32+
33+
export function FeatureFlagProvider<
34+
Environment extends string,
35+
Schema extends FeatureFlagSchema,
36+
>({ children, hydratedOverrides, ...options }: {
37+
featureFlags: FeatureFlags<Environment, Schema>;
38+
options?: never;
39+
hydratedOverrides?: Overrides<Schema>;
40+
children: ReactNode;
41+
} | {
42+
featureFlags?: never;
43+
options: ConstructorParameters<typeof FeatureFlags<Environment, Schema>>[0];
44+
hydratedOverrides?: Overrides<Schema>;
45+
children: ReactNode;
46+
}) {
47+
const featureFlags = options.featureFlags ||
48+
new FeatureFlags<Environment, Schema>(options.options);
49+
50+
const state = useSyncExternalStore<TFeatureFlags<Schema>>(
51+
(listener) => {
52+
featureFlags.subscribe(listener);
53+
return () => featureFlags.unsubscribe(listener);
54+
},
55+
() => ({ ...featureFlags.store }),
56+
);
57+
58+
const value = { featureFlags, state };
59+
60+
useEffect(() => {
61+
if (!hydratedOverrides) return;
62+
const flags = FeatureFlags.listOptionsFromSchema(featureFlags.schema);
63+
flags.forEach((flag) => {
64+
const override = hydratedOverrides(flag);
65+
if (FeatureFlags.isValidValue(override)) {
66+
try {
67+
featureFlags.set(flag, override);
68+
} catch (_e) {
69+
/* ignore readonly errors here */
70+
}
71+
}
72+
});
73+
}, []);
74+
75+
return FeatureFlagContext.Provider({ value, children });
76+
}
77+
78+
// deno-lint-ignore no-explicit-any
79+
export function useFeatureFlags<Schema extends FeatureFlagSchema = any>(
80+
_: { schema: Schema },
81+
) {
82+
const { featureFlags, state } = useContext(FeatureFlagContext) as State<
83+
"",
84+
Schema
85+
>;
86+
87+
function setState(updates: Partial<TFeatureFlags<Schema>>) {
88+
const flags = Object.keys(updates) as (keyof typeof updates)[];
89+
flags.forEach((flag) => {
90+
const v = updates[flag];
91+
if (FeatureFlags.isValidValue(v)) {
92+
featureFlags.set(flag, v);
93+
}
94+
});
95+
}
96+
97+
return [state, setState];
98+
}

0 commit comments

Comments
 (0)