flagmint-react-sdk 0.5.0 → 0.7.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/dist/client.cjs +24 -50
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +16 -6
- package/dist/client.d.ts +16 -6
- package/dist/client.js +24 -50
- package/dist/client.js.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/package.json +2 -2
- package/README.md +0 -609
package/dist/client.cjs
CHANGED
|
@@ -31,47 +31,28 @@ function FlagmintProvider({
|
|
|
31
31
|
const store = react.useMemo(() => createFlagStore(initialFlags), [initialFlags]);
|
|
32
32
|
const clientRef = react.useRef(null);
|
|
33
33
|
const initPromiseRef = react.useRef(null);
|
|
34
|
-
react.useRef(
|
|
34
|
+
const unsubscribeRef = react.useRef(null);
|
|
35
35
|
const init = async () => {
|
|
36
36
|
if (clientRef.current) return clientRef.current;
|
|
37
37
|
if (initPromiseRef.current) return initPromiseRef.current;
|
|
38
|
-
const { setClient, setReady, setInitialized } = store.getState();
|
|
38
|
+
const { setClient, setReady, setInitialized, setFlags } = store.getState();
|
|
39
39
|
initPromiseRef.current = (async () => {
|
|
40
40
|
try {
|
|
41
|
-
const
|
|
42
|
-
await
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this._flags[key] = value;
|
|
53
|
-
store.getState().setFlag(key, value);
|
|
54
|
-
return value;
|
|
55
|
-
},
|
|
56
|
-
// Override updateContext to trigger flag refresh
|
|
57
|
-
updateContext: function(context) {
|
|
58
|
-
originalClient.updateContext(context);
|
|
59
|
-
setTimeout(() => {
|
|
60
|
-
Object.keys(this._flags).forEach((key) => {
|
|
61
|
-
const newValue = originalClient.getFlag(key);
|
|
62
|
-
if (this._flags[key] !== newValue) {
|
|
63
|
-
this._flags[key] = newValue;
|
|
64
|
-
store.getState().setFlag(key, newValue);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}, 100);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
clientRef.current = clientWrapper;
|
|
71
|
-
setClient(clientWrapper);
|
|
41
|
+
const client = new flagmintJsSdk.FlagClient(options);
|
|
42
|
+
await client.ready();
|
|
43
|
+
const initialClientFlags = client.getFlags();
|
|
44
|
+
setFlags(initialClientFlags);
|
|
45
|
+
const unsubscribe = client.subscribe((updatedFlags) => {
|
|
46
|
+
console.log("FlagmintProvider - flags updated:", updatedFlags);
|
|
47
|
+
setFlags(updatedFlags);
|
|
48
|
+
});
|
|
49
|
+
unsubscribeRef.current = unsubscribe;
|
|
50
|
+
clientRef.current = client;
|
|
51
|
+
setClient(client);
|
|
72
52
|
setReady(true);
|
|
73
53
|
setInitialized(true);
|
|
74
|
-
|
|
54
|
+
console.log("FlagmintProvider - initialized with flags:", initialClientFlags);
|
|
55
|
+
return client;
|
|
75
56
|
} catch (error) {
|
|
76
57
|
console.error("Failed to initialize Flagmint client:", error);
|
|
77
58
|
throw error;
|
|
@@ -87,6 +68,10 @@ function FlagmintProvider({
|
|
|
87
68
|
}
|
|
88
69
|
return () => {
|
|
89
70
|
var _a, _b;
|
|
71
|
+
if (unsubscribeRef.current) {
|
|
72
|
+
unsubscribeRef.current();
|
|
73
|
+
unsubscribeRef.current = null;
|
|
74
|
+
}
|
|
90
75
|
if (clientRef.current) {
|
|
91
76
|
(_b = (_a = clientRef.current).destroy) == null ? void 0 : _b.call(_a);
|
|
92
77
|
clientRef.current = null;
|
|
@@ -125,25 +110,14 @@ function useFlags() {
|
|
|
125
110
|
}
|
|
126
111
|
function useFlag(key, fallback) {
|
|
127
112
|
const store = useFlagmintStore();
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const flagValue = client.getFlag(key, fallback);
|
|
132
|
-
const currentValue = store.getState().flags[key];
|
|
133
|
-
if (currentValue !== flagValue) {
|
|
134
|
-
store.getState().setFlag(key, flagValue);
|
|
135
|
-
}
|
|
136
|
-
return flagValue;
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.warn(`Failed to get flag '${key}' from client:`, error);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const cachedValue = zustand.useStore(store, (state) => state.flags[key]);
|
|
142
|
-
return cachedValue != null ? cachedValue : fallback;
|
|
113
|
+
const flagValue = zustand.useStore(store, (state) => state.flags[key]);
|
|
114
|
+
return flagValue != null ? flagValue : fallback;
|
|
143
115
|
}
|
|
144
116
|
function useFlagmintReady() {
|
|
145
117
|
const store = useFlagmintStore();
|
|
146
|
-
|
|
118
|
+
const isReady = zustand.useStore(store, (state) => state.isReady);
|
|
119
|
+
const isInitialized = zustand.useStore(store, (state) => state.isInitialized);
|
|
120
|
+
return isReady && isInitialized;
|
|
147
121
|
}
|
|
148
122
|
|
|
149
123
|
exports.FlagmintProvider = FlagmintProvider;
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store/store.ts","../src/providers/FlagmintProvider.tsx","../src/hooks/index.ts"],"names":["createStore","createContext","useMemo","useRef","FlagClient","useEffect","useContext","useStore"],"mappings":"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","file":"client.cjs","sourcesContent":["'use client';\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { Flags, FlagStore } from \"@/types\";\n\n\nexport function createFlagStore(initialFlags: Flags = {}): StoreApi<FlagStore> {\n return createStore<FlagStore>((set, get) => ({\n flags: initialFlags,\n client: null,\n isInitialized: false,\n isReady: false,\n setFlags: (flags) => set({ flags }),\n setFlag: (key, value) => set((state) => ({\n flags: { ...state.flags, [key]: value }\n })),\n setClient: (client) => set({ client }),\n setInitialized: (initialized) => set({ isInitialized: initialized }),\n setReady: (ready) => set({ isReady: ready })\n }))\n}\n","'use client';\nimport { FlagClient } from \"flagmint-js-sdk\"\nimport { useMemo, useRef, useEffect, createContext } from \"react\"\nimport { createFlagStore } from \"@/store/store\"\nimport { FlagmintProviderProps, Flags, FlagStore } from \"@/types\"\nimport { StoreApi } from \"zustand\"\n\nexport const FlagmintStoreContext = createContext<StoreApi<FlagStore> | null>(null)\n\n\nexport function FlagmintProvider<\n T extends FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags },\n C extends Record<string, any> = Record<string, any>\n>({\n children,\n options,\n initialFlags = {},\n deferInitialization = false\n}: FlagmintProviderProps<T, C>) {\n // Create a new store instance for each provider (per-request in SSR)\n const store = useMemo(() => createFlagStore(initialFlags), [initialFlags])\n \n // Use ref to avoid recreating client on every render\n const clientRef = useRef<FlagClient<T, C> | null>(null)\n const initPromiseRef = useRef<Promise<FlagClient<T, C>> | null>(null)\n const flagsRef = useRef<Flags>(initialFlags)\n \n // Initialization function similar to Vue plugin\n const init = async (): Promise<FlagClient<T, C>> => {\n if (clientRef.current) return clientRef.current // Avoid re-init\n \n // Return existing promise if init is already in progress\n if (initPromiseRef.current) return initPromiseRef.current\n \n const { setClient, setReady, setInitialized } = store.getState()\n \n initPromiseRef.current = (async () => {\n try {\n // Create a custom FlagClient that exposes flag updates\n const originalClient = new FlagClient<T, C>(options)\n await originalClient.ready()\n \n // Create a wrapper that tracks flags internally\n const clientWrapper = {\n ...originalClient,\n _flags: {} as Flags,\n getFlags: function() {\n return this._flags\n },\n // Override getFlag to track individual flags\n getFlag: function<K extends string>(key: K, fallback?: T) {\n const value = originalClient.getFlag(key as any, fallback)\n this._flags[key] = value\n store.getState().setFlag(key, value)\n return value\n },\n // Override updateContext to trigger flag refresh\n updateContext: function(context: C) {\n originalClient.updateContext(context)\n // After context update, we should refresh our tracked flags\n // This is a limitation of the current API\n setTimeout(() => {\n // Force a re-evaluation of flags we've previously requested\n Object.keys(this._flags).forEach(key => {\n const newValue = originalClient.getFlag(key as any)\n if (this._flags[key] !== newValue) {\n this._flags[key] = newValue\n store.getState().setFlag(key, newValue)\n }\n })\n }, 100)\n }\n } as FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags }\n \n clientRef.current = clientWrapper\n setClient(clientWrapper)\n setReady(true)\n setInitialized(true)\n \n return clientWrapper\n } catch (error) {\n console.error('Failed to initialize Flagmint client:', error)\n throw error\n }\n })()\n \n return initPromiseRef.current\n }\n\n useEffect(() => {\n if (!deferInitialization) {\n init().catch(error => {\n console.error('Auto-initialization failed:', error)\n })\n }\n\n return () => {\n if (clientRef.current) {\n clientRef.current.destroy?.()\n clientRef.current = null\n initPromiseRef.current = null\n }\n }\n }, [deferInitialization])\n\n return (\n <FlagmintStoreContext.Provider value={store}>\n {children}\n </FlagmintStoreContext.Provider>\n )\n}\n","'use client';\nimport { FlagValue } from \"flagmint-js-sdk\"\nimport { useContext } from \"react\"\nimport { StoreApi, useStore } from \"zustand\"\nimport { FlagmintStoreContext } from \"@/providers/FlagmintProvider\"\nimport { FlagStore, Flags } from \"@/types\"\n\n// Helper to get the store from context\nexport function useFlagmintStore(): StoreApi<FlagStore> {\n const store = useContext(FlagmintStoreContext)\n if (!store) {\n throw new Error('useFlagmintStore must be used within a FlagmintProvider')\n }\n return store\n}\n\n// Hook to get the client instance and update context\nexport function useFlagmint() {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n\n const updateContext = async (context: Record<string, any>) => {\n if (!client) {\n throw new Error('Flagmint client not initialized')\n }\n await client.updateContext(context)\n }\n\n return {\n client,\n isInitialized,\n updateContext\n }\n}\n\n// Hook to get all flags - only re-renders when flags change\nexport function useFlags(): Flags {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags)\n}\n\n\nexport function useFlag<T = FlagValue>(\n key: string,\n fallback?: T\n): T {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n \n // Always try to get the latest value from client first\n if (client) {\n try {\n // Cast fallback to satisfy the client's type requirements\n const flagValue = client.getFlag(key as any, fallback as any)\n \n // Update store cache with the fresh value\n const currentValue = store.getState().flags[key]\n if (currentValue !== flagValue) {\n store.getState().setFlag(key, flagValue as FlagValue)\n }\n \n return flagValue as T\n } catch (error) {\n console.warn(`Failed to get flag '${key}' from client:`, error)\n // Fall through to store cache\n }\n }\n \n // Fallback to store cache or default\n const cachedValue = useStore(store, (state) => state.flags[key])\n return (cachedValue ?? fallback) as T\n}\n\n// Hook to check if the client is ready\nexport function useFlagmintReady(): boolean {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.isInitialized && state.client !== null)\n}"]}
|
|
1
|
+
{"version":3,"sources":["../src/store/store.ts","../src/providers/FlagmintProvider.tsx","../src/hooks/index.ts"],"names":["createStore","createContext","useMemo","useRef","FlagClient","useEffect","useContext","useStore"],"mappings":"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","file":"client.cjs","sourcesContent":["'use client';\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { Flags, FlagStore } from \"@/types\";\n\n\nexport function createFlagStore(initialFlags: Flags = {}): StoreApi<FlagStore> {\n return createStore<FlagStore>((set, get) => ({\n flags: initialFlags,\n client: null,\n isInitialized: false,\n isReady: false,\n setFlags: (flags) => set({ flags }),\n setFlag: (key, value) => set((state) => ({\n flags: { ...state.flags, [key]: value }\n })),\n setClient: (client) => set({ client }),\n setInitialized: (initialized) => set({ isInitialized: initialized }),\n setReady: (ready) => set({ isReady: ready })\n }))\n}\n","'use client';\nimport { FlagClient } from \"flagmint-js-sdk\"\nimport { useMemo, useRef, useEffect, createContext } from \"react\"\nimport { createFlagStore } from \"@/store/store\"\nimport { FlagmintProviderProps, Flags, FlagStore } from \"@/types\"\nimport { StoreApi } from \"zustand\"\n\nexport const FlagmintStoreContext = createContext<StoreApi<FlagStore> | null>(null)\n\n/**\n * Provides the Flagmint flag store to React components and keeps it synced with FlagClient updates.\n * Lifecycle overview:\n * - App loads → FlagClient constructed → initialize() begins.\n * - Cached flags applied → subscribers notified → React renders cached values.\n * - Transport set up → initial fetch → store updated → subscribers notified.\n * - Subsequent server updates propagate via client.subscribe to re-render consumers.\n * - When context changes (updateContext), flags are re-fetched and the store is refreshed.\n *\n * @param props Component props: children, client options, optional initial flags, and deferInitialization flag.\n * @returns A provider that supplies the flag store context to its children.\n */\nexport function FlagmintProvider<\n T extends FlagClient<T, C>,\n C extends Record<string, any> = Record<string, any>\n>({\n children,\n options,\n initialFlags = {},\n deferInitialization = false\n}: FlagmintProviderProps<T, C>) {\n // Create a new store instance for each provider (per-request in SSR)\n const store = useMemo(() => createFlagStore(initialFlags), [initialFlags])\n \n // Use ref to avoid recreating client on every render\n const clientRef = useRef<FlagClient<T, C> | null>(null)\n const initPromiseRef = useRef<Promise<FlagClient<T, C>> | null>(null)\n const unsubscribeRef = useRef<(() => void) | null>(null)\n \n // Initialization function similar to Vue plugin\n const init = async (): Promise<FlagClient<T, C>> => {\n if (clientRef.current) return clientRef.current // Avoid re-init\n \n // Return existing promise if init is already in progress\n if (initPromiseRef.current) return initPromiseRef.current\n \n const { setClient, setReady, setInitialized, setFlags } = store.getState()\n \n initPromiseRef.current = (async () => {\n try {\n // Create the FlagClient\n const client = new FlagClient<T, C>(options)\n \n // Wait for client to be ready\n await client.ready()\n \n // Get initial flags from client\n const initialClientFlags = client.getFlags()\n setFlags(initialClientFlags)\n \n // Subscribe to flag changes - THIS IS THE KEY FIX\n const unsubscribe = client.subscribe((updatedFlags: Flags) => {\n console.log('FlagmintProvider - flags updated:', updatedFlags)\n setFlags(updatedFlags)\n })\n \n unsubscribeRef.current = unsubscribe\n \n clientRef.current = client\n setClient(client)\n setReady(true)\n setInitialized(true)\n \n console.log('FlagmintProvider - initialized with flags:', initialClientFlags)\n \n return client\n } catch (error) {\n console.error('Failed to initialize Flagmint client:', error)\n throw error\n }\n })()\n \n return initPromiseRef.current\n }\n\n useEffect(() => {\n if (!deferInitialization) {\n init().catch(error => {\n console.error('Auto-initialization failed:', error)\n })\n }\n\n return () => {\n // Unsubscribe from flag changes\n if (unsubscribeRef.current) {\n unsubscribeRef.current()\n unsubscribeRef.current = null\n }\n \n // Destroy client\n if (clientRef.current) {\n clientRef.current.destroy?.()\n clientRef.current = null\n initPromiseRef.current = null\n }\n }\n }, [deferInitialization])\n\n return (\n <FlagmintStoreContext.Provider value={store}>\n {children}\n </FlagmintStoreContext.Provider>\n )\n}\n","'use client';\nimport { FlagValue } from \"flagmint-js-sdk\"\nimport { useContext } from \"react\"\nimport { StoreApi, useStore } from \"zustand\"\nimport { FlagmintStoreContext } from \"@/providers/FlagmintProvider\"\nimport { FlagStore, Flags } from \"@/types\"\n\n// Helper to get the store from context\nexport function useFlagmintStore(): StoreApi<FlagStore> {\n const store = useContext(FlagmintStoreContext)\n if (!store) {\n throw new Error('useFlagmintStore must be used within a FlagmintProvider')\n }\n return store\n}\n\n// Hook to get the client instance and update context\nexport function useFlagmint() {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n\n const updateContext = async (context: Record<string, any>) => {\n if (!client) {\n throw new Error('Flagmint client not initialized')\n }\n await client.updateContext(context)\n }\n\n return {\n client,\n isInitialized,\n updateContext\n }\n}\n\n// Hook to get all flags - only re-renders when flags change\nexport function useFlags(): Flags {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags)\n}\n\n// Simplified useFlag - just read from the reactive store\nexport function useFlag<T = FlagValue>(\n key: string,\n fallback?: T\n): T {\n const store = useFlagmintStore()\n \n // Subscribe to this specific flag in the store\n const flagValue = useStore(store, (state) => state.flags[key])\n \n // Return the flag value or fallback\n return (flagValue ?? fallback) as T\n}\n\n// Hook to check if the client is ready\nexport function useFlagmintReady(): boolean {\n const store = useFlagmintStore()\n \n // Subscribe to both isReady and isInitialized\n const isReady = useStore(store, (state) => state.isReady)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n \n return isReady && isInitialized\n}"]}
|
package/dist/client.d.cts
CHANGED
|
@@ -2,14 +2,24 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import * as flagmint_js_sdk from 'flagmint-js-sdk';
|
|
4
4
|
import { FlagClient, FlagValue } from 'flagmint-js-sdk';
|
|
5
|
-
import { FlagStore, Flags
|
|
5
|
+
import { FlagmintProviderProps, FlagStore, Flags } from './index.cjs';
|
|
6
|
+
export { CoreClientLike, FlagValue } from './index.cjs';
|
|
6
7
|
import { StoreApi } from 'zustand';
|
|
7
8
|
|
|
8
9
|
declare const FlagmintStoreContext: react.Context<StoreApi<FlagStore> | null>;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Provides the Flagmint flag store to React components and keeps it synced with FlagClient updates.
|
|
12
|
+
* Lifecycle overview:
|
|
13
|
+
* - App loads → FlagClient constructed → initialize() begins.
|
|
14
|
+
* - Cached flags applied → subscribers notified → React renders cached values.
|
|
15
|
+
* - Transport set up → initial fetch → store updated → subscribers notified.
|
|
16
|
+
* - Subsequent server updates propagate via client.subscribe to re-render consumers.
|
|
17
|
+
* - When context changes (updateContext), flags are re-fetched and the store is refreshed.
|
|
18
|
+
*
|
|
19
|
+
* @param props Component props: children, client options, optional initial flags, and deferInitialization flag.
|
|
20
|
+
* @returns A provider that supplies the flag store context to its children.
|
|
21
|
+
*/
|
|
22
|
+
declare function FlagmintProvider<T extends FlagClient<T, C>, C extends Record<string, any> = Record<string, any>>({ children, options, initialFlags, deferInitialization }: FlagmintProviderProps<T, C>): react_jsx_runtime.JSX.Element;
|
|
13
23
|
|
|
14
24
|
declare function useFlagmintStore(): StoreApi<FlagStore>;
|
|
15
25
|
declare function useFlagmint(): {
|
|
@@ -21,4 +31,4 @@ declare function useFlags(): Flags;
|
|
|
21
31
|
declare function useFlag<T = FlagValue>(key: string, fallback?: T): T;
|
|
22
32
|
declare function useFlagmintReady(): boolean;
|
|
23
33
|
|
|
24
|
-
export { FlagmintProvider, FlagmintStoreContext, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
|
34
|
+
export { FlagStore, FlagmintProvider, FlagmintProviderProps, FlagmintStoreContext, Flags, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
package/dist/client.d.ts
CHANGED
|
@@ -2,14 +2,24 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import * as flagmint_js_sdk from 'flagmint-js-sdk';
|
|
4
4
|
import { FlagClient, FlagValue } from 'flagmint-js-sdk';
|
|
5
|
-
import { FlagStore, Flags
|
|
5
|
+
import { FlagmintProviderProps, FlagStore, Flags } from './index.js';
|
|
6
|
+
export { CoreClientLike, FlagValue } from './index.js';
|
|
6
7
|
import { StoreApi } from 'zustand';
|
|
7
8
|
|
|
8
9
|
declare const FlagmintStoreContext: react.Context<StoreApi<FlagStore> | null>;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Provides the Flagmint flag store to React components and keeps it synced with FlagClient updates.
|
|
12
|
+
* Lifecycle overview:
|
|
13
|
+
* - App loads → FlagClient constructed → initialize() begins.
|
|
14
|
+
* - Cached flags applied → subscribers notified → React renders cached values.
|
|
15
|
+
* - Transport set up → initial fetch → store updated → subscribers notified.
|
|
16
|
+
* - Subsequent server updates propagate via client.subscribe to re-render consumers.
|
|
17
|
+
* - When context changes (updateContext), flags are re-fetched and the store is refreshed.
|
|
18
|
+
*
|
|
19
|
+
* @param props Component props: children, client options, optional initial flags, and deferInitialization flag.
|
|
20
|
+
* @returns A provider that supplies the flag store context to its children.
|
|
21
|
+
*/
|
|
22
|
+
declare function FlagmintProvider<T extends FlagClient<T, C>, C extends Record<string, any> = Record<string, any>>({ children, options, initialFlags, deferInitialization }: FlagmintProviderProps<T, C>): react_jsx_runtime.JSX.Element;
|
|
13
23
|
|
|
14
24
|
declare function useFlagmintStore(): StoreApi<FlagStore>;
|
|
15
25
|
declare function useFlagmint(): {
|
|
@@ -21,4 +31,4 @@ declare function useFlags(): Flags;
|
|
|
21
31
|
declare function useFlag<T = FlagValue>(key: string, fallback?: T): T;
|
|
22
32
|
declare function useFlagmintReady(): boolean;
|
|
23
33
|
|
|
24
|
-
export { FlagmintProvider, FlagmintStoreContext, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
|
34
|
+
export { FlagStore, FlagmintProvider, FlagmintProviderProps, FlagmintStoreContext, Flags, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
package/dist/client.js
CHANGED
|
@@ -29,47 +29,28 @@ function FlagmintProvider({
|
|
|
29
29
|
const store = useMemo(() => createFlagStore(initialFlags), [initialFlags]);
|
|
30
30
|
const clientRef = useRef(null);
|
|
31
31
|
const initPromiseRef = useRef(null);
|
|
32
|
-
useRef(
|
|
32
|
+
const unsubscribeRef = useRef(null);
|
|
33
33
|
const init = async () => {
|
|
34
34
|
if (clientRef.current) return clientRef.current;
|
|
35
35
|
if (initPromiseRef.current) return initPromiseRef.current;
|
|
36
|
-
const { setClient, setReady, setInitialized } = store.getState();
|
|
36
|
+
const { setClient, setReady, setInitialized, setFlags } = store.getState();
|
|
37
37
|
initPromiseRef.current = (async () => {
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
40
|
-
await
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this._flags[key] = value;
|
|
51
|
-
store.getState().setFlag(key, value);
|
|
52
|
-
return value;
|
|
53
|
-
},
|
|
54
|
-
// Override updateContext to trigger flag refresh
|
|
55
|
-
updateContext: function(context) {
|
|
56
|
-
originalClient.updateContext(context);
|
|
57
|
-
setTimeout(() => {
|
|
58
|
-
Object.keys(this._flags).forEach((key) => {
|
|
59
|
-
const newValue = originalClient.getFlag(key);
|
|
60
|
-
if (this._flags[key] !== newValue) {
|
|
61
|
-
this._flags[key] = newValue;
|
|
62
|
-
store.getState().setFlag(key, newValue);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}, 100);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
clientRef.current = clientWrapper;
|
|
69
|
-
setClient(clientWrapper);
|
|
39
|
+
const client = new FlagClient(options);
|
|
40
|
+
await client.ready();
|
|
41
|
+
const initialClientFlags = client.getFlags();
|
|
42
|
+
setFlags(initialClientFlags);
|
|
43
|
+
const unsubscribe = client.subscribe((updatedFlags) => {
|
|
44
|
+
console.log("FlagmintProvider - flags updated:", updatedFlags);
|
|
45
|
+
setFlags(updatedFlags);
|
|
46
|
+
});
|
|
47
|
+
unsubscribeRef.current = unsubscribe;
|
|
48
|
+
clientRef.current = client;
|
|
49
|
+
setClient(client);
|
|
70
50
|
setReady(true);
|
|
71
51
|
setInitialized(true);
|
|
72
|
-
|
|
52
|
+
console.log("FlagmintProvider - initialized with flags:", initialClientFlags);
|
|
53
|
+
return client;
|
|
73
54
|
} catch (error) {
|
|
74
55
|
console.error("Failed to initialize Flagmint client:", error);
|
|
75
56
|
throw error;
|
|
@@ -85,6 +66,10 @@ function FlagmintProvider({
|
|
|
85
66
|
}
|
|
86
67
|
return () => {
|
|
87
68
|
var _a, _b;
|
|
69
|
+
if (unsubscribeRef.current) {
|
|
70
|
+
unsubscribeRef.current();
|
|
71
|
+
unsubscribeRef.current = null;
|
|
72
|
+
}
|
|
88
73
|
if (clientRef.current) {
|
|
89
74
|
(_b = (_a = clientRef.current).destroy) == null ? void 0 : _b.call(_a);
|
|
90
75
|
clientRef.current = null;
|
|
@@ -123,25 +108,14 @@ function useFlags() {
|
|
|
123
108
|
}
|
|
124
109
|
function useFlag(key, fallback) {
|
|
125
110
|
const store = useFlagmintStore();
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const flagValue = client.getFlag(key, fallback);
|
|
130
|
-
const currentValue = store.getState().flags[key];
|
|
131
|
-
if (currentValue !== flagValue) {
|
|
132
|
-
store.getState().setFlag(key, flagValue);
|
|
133
|
-
}
|
|
134
|
-
return flagValue;
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.warn(`Failed to get flag '${key}' from client:`, error);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const cachedValue = useStore(store, (state) => state.flags[key]);
|
|
140
|
-
return cachedValue != null ? cachedValue : fallback;
|
|
111
|
+
const flagValue = useStore(store, (state) => state.flags[key]);
|
|
112
|
+
return flagValue != null ? flagValue : fallback;
|
|
141
113
|
}
|
|
142
114
|
function useFlagmintReady() {
|
|
143
115
|
const store = useFlagmintStore();
|
|
144
|
-
|
|
116
|
+
const isReady = useStore(store, (state) => state.isReady);
|
|
117
|
+
const isInitialized = useStore(store, (state) => state.isInitialized);
|
|
118
|
+
return isReady && isInitialized;
|
|
145
119
|
}
|
|
146
120
|
|
|
147
121
|
export { FlagmintProvider, FlagmintStoreContext, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store/store.ts","../src/providers/FlagmintProvider.tsx","../src/hooks/index.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["'use client';\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { Flags, FlagStore } from \"@/types\";\n\n\nexport function createFlagStore(initialFlags: Flags = {}): StoreApi<FlagStore> {\n return createStore<FlagStore>((set, get) => ({\n flags: initialFlags,\n client: null,\n isInitialized: false,\n isReady: false,\n setFlags: (flags) => set({ flags }),\n setFlag: (key, value) => set((state) => ({\n flags: { ...state.flags, [key]: value }\n })),\n setClient: (client) => set({ client }),\n setInitialized: (initialized) => set({ isInitialized: initialized }),\n setReady: (ready) => set({ isReady: ready })\n }))\n}\n","'use client';\nimport { FlagClient } from \"flagmint-js-sdk\"\nimport { useMemo, useRef, useEffect, createContext } from \"react\"\nimport { createFlagStore } from \"@/store/store\"\nimport { FlagmintProviderProps, Flags, FlagStore } from \"@/types\"\nimport { StoreApi } from \"zustand\"\n\nexport const FlagmintStoreContext = createContext<StoreApi<FlagStore> | null>(null)\n\n\nexport function FlagmintProvider<\n T extends FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags },\n C extends Record<string, any> = Record<string, any>\n>({\n children,\n options,\n initialFlags = {},\n deferInitialization = false\n}: FlagmintProviderProps<T, C>) {\n // Create a new store instance for each provider (per-request in SSR)\n const store = useMemo(() => createFlagStore(initialFlags), [initialFlags])\n \n // Use ref to avoid recreating client on every render\n const clientRef = useRef<FlagClient<T, C> | null>(null)\n const initPromiseRef = useRef<Promise<FlagClient<T, C>> | null>(null)\n const flagsRef = useRef<Flags>(initialFlags)\n \n // Initialization function similar to Vue plugin\n const init = async (): Promise<FlagClient<T, C>> => {\n if (clientRef.current) return clientRef.current // Avoid re-init\n \n // Return existing promise if init is already in progress\n if (initPromiseRef.current) return initPromiseRef.current\n \n const { setClient, setReady, setInitialized } = store.getState()\n \n initPromiseRef.current = (async () => {\n try {\n // Create a custom FlagClient that exposes flag updates\n const originalClient = new FlagClient<T, C>(options)\n await originalClient.ready()\n \n // Create a wrapper that tracks flags internally\n const clientWrapper = {\n ...originalClient,\n _flags: {} as Flags,\n getFlags: function() {\n return this._flags\n },\n // Override getFlag to track individual flags\n getFlag: function<K extends string>(key: K, fallback?: T) {\n const value = originalClient.getFlag(key as any, fallback)\n this._flags[key] = value\n store.getState().setFlag(key, value)\n return value\n },\n // Override updateContext to trigger flag refresh\n updateContext: function(context: C) {\n originalClient.updateContext(context)\n // After context update, we should refresh our tracked flags\n // This is a limitation of the current API\n setTimeout(() => {\n // Force a re-evaluation of flags we've previously requested\n Object.keys(this._flags).forEach(key => {\n const newValue = originalClient.getFlag(key as any)\n if (this._flags[key] !== newValue) {\n this._flags[key] = newValue\n store.getState().setFlag(key, newValue)\n }\n })\n }, 100)\n }\n } as FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags }\n \n clientRef.current = clientWrapper\n setClient(clientWrapper)\n setReady(true)\n setInitialized(true)\n \n return clientWrapper\n } catch (error) {\n console.error('Failed to initialize Flagmint client:', error)\n throw error\n }\n })()\n \n return initPromiseRef.current\n }\n\n useEffect(() => {\n if (!deferInitialization) {\n init().catch(error => {\n console.error('Auto-initialization failed:', error)\n })\n }\n\n return () => {\n if (clientRef.current) {\n clientRef.current.destroy?.()\n clientRef.current = null\n initPromiseRef.current = null\n }\n }\n }, [deferInitialization])\n\n return (\n <FlagmintStoreContext.Provider value={store}>\n {children}\n </FlagmintStoreContext.Provider>\n )\n}\n","'use client';\nimport { FlagValue } from \"flagmint-js-sdk\"\nimport { useContext } from \"react\"\nimport { StoreApi, useStore } from \"zustand\"\nimport { FlagmintStoreContext } from \"@/providers/FlagmintProvider\"\nimport { FlagStore, Flags } from \"@/types\"\n\n// Helper to get the store from context\nexport function useFlagmintStore(): StoreApi<FlagStore> {\n const store = useContext(FlagmintStoreContext)\n if (!store) {\n throw new Error('useFlagmintStore must be used within a FlagmintProvider')\n }\n return store\n}\n\n// Hook to get the client instance and update context\nexport function useFlagmint() {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n\n const updateContext = async (context: Record<string, any>) => {\n if (!client) {\n throw new Error('Flagmint client not initialized')\n }\n await client.updateContext(context)\n }\n\n return {\n client,\n isInitialized,\n updateContext\n }\n}\n\n// Hook to get all flags - only re-renders when flags change\nexport function useFlags(): Flags {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags)\n}\n\n\nexport function useFlag<T = FlagValue>(\n key: string,\n fallback?: T\n): T {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n \n // Always try to get the latest value from client first\n if (client) {\n try {\n // Cast fallback to satisfy the client's type requirements\n const flagValue = client.getFlag(key as any, fallback as any)\n \n // Update store cache with the fresh value\n const currentValue = store.getState().flags[key]\n if (currentValue !== flagValue) {\n store.getState().setFlag(key, flagValue as FlagValue)\n }\n \n return flagValue as T\n } catch (error) {\n console.warn(`Failed to get flag '${key}' from client:`, error)\n // Fall through to store cache\n }\n }\n \n // Fallback to store cache or default\n const cachedValue = useStore(store, (state) => state.flags[key])\n return (cachedValue ?? fallback) as T\n}\n\n// Hook to check if the client is ready\nexport function useFlagmintReady(): boolean {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.isInitialized && state.client !== null)\n}"]}
|
|
1
|
+
{"version":3,"sources":["../src/store/store.ts","../src/providers/FlagmintProvider.tsx","../src/hooks/index.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["'use client';\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { Flags, FlagStore } from \"@/types\";\n\n\nexport function createFlagStore(initialFlags: Flags = {}): StoreApi<FlagStore> {\n return createStore<FlagStore>((set, get) => ({\n flags: initialFlags,\n client: null,\n isInitialized: false,\n isReady: false,\n setFlags: (flags) => set({ flags }),\n setFlag: (key, value) => set((state) => ({\n flags: { ...state.flags, [key]: value }\n })),\n setClient: (client) => set({ client }),\n setInitialized: (initialized) => set({ isInitialized: initialized }),\n setReady: (ready) => set({ isReady: ready })\n }))\n}\n","'use client';\nimport { FlagClient } from \"flagmint-js-sdk\"\nimport { useMemo, useRef, useEffect, createContext } from \"react\"\nimport { createFlagStore } from \"@/store/store\"\nimport { FlagmintProviderProps, Flags, FlagStore } from \"@/types\"\nimport { StoreApi } from \"zustand\"\n\nexport const FlagmintStoreContext = createContext<StoreApi<FlagStore> | null>(null)\n\n/**\n * Provides the Flagmint flag store to React components and keeps it synced with FlagClient updates.\n * Lifecycle overview:\n * - App loads → FlagClient constructed → initialize() begins.\n * - Cached flags applied → subscribers notified → React renders cached values.\n * - Transport set up → initial fetch → store updated → subscribers notified.\n * - Subsequent server updates propagate via client.subscribe to re-render consumers.\n * - When context changes (updateContext), flags are re-fetched and the store is refreshed.\n *\n * @param props Component props: children, client options, optional initial flags, and deferInitialization flag.\n * @returns A provider that supplies the flag store context to its children.\n */\nexport function FlagmintProvider<\n T extends FlagClient<T, C>,\n C extends Record<string, any> = Record<string, any>\n>({\n children,\n options,\n initialFlags = {},\n deferInitialization = false\n}: FlagmintProviderProps<T, C>) {\n // Create a new store instance for each provider (per-request in SSR)\n const store = useMemo(() => createFlagStore(initialFlags), [initialFlags])\n \n // Use ref to avoid recreating client on every render\n const clientRef = useRef<FlagClient<T, C> | null>(null)\n const initPromiseRef = useRef<Promise<FlagClient<T, C>> | null>(null)\n const unsubscribeRef = useRef<(() => void) | null>(null)\n \n // Initialization function similar to Vue plugin\n const init = async (): Promise<FlagClient<T, C>> => {\n if (clientRef.current) return clientRef.current // Avoid re-init\n \n // Return existing promise if init is already in progress\n if (initPromiseRef.current) return initPromiseRef.current\n \n const { setClient, setReady, setInitialized, setFlags } = store.getState()\n \n initPromiseRef.current = (async () => {\n try {\n // Create the FlagClient\n const client = new FlagClient<T, C>(options)\n \n // Wait for client to be ready\n await client.ready()\n \n // Get initial flags from client\n const initialClientFlags = client.getFlags()\n setFlags(initialClientFlags)\n \n // Subscribe to flag changes - THIS IS THE KEY FIX\n const unsubscribe = client.subscribe((updatedFlags: Flags) => {\n console.log('FlagmintProvider - flags updated:', updatedFlags)\n setFlags(updatedFlags)\n })\n \n unsubscribeRef.current = unsubscribe\n \n clientRef.current = client\n setClient(client)\n setReady(true)\n setInitialized(true)\n \n console.log('FlagmintProvider - initialized with flags:', initialClientFlags)\n \n return client\n } catch (error) {\n console.error('Failed to initialize Flagmint client:', error)\n throw error\n }\n })()\n \n return initPromiseRef.current\n }\n\n useEffect(() => {\n if (!deferInitialization) {\n init().catch(error => {\n console.error('Auto-initialization failed:', error)\n })\n }\n\n return () => {\n // Unsubscribe from flag changes\n if (unsubscribeRef.current) {\n unsubscribeRef.current()\n unsubscribeRef.current = null\n }\n \n // Destroy client\n if (clientRef.current) {\n clientRef.current.destroy?.()\n clientRef.current = null\n initPromiseRef.current = null\n }\n }\n }, [deferInitialization])\n\n return (\n <FlagmintStoreContext.Provider value={store}>\n {children}\n </FlagmintStoreContext.Provider>\n )\n}\n","'use client';\nimport { FlagValue } from \"flagmint-js-sdk\"\nimport { useContext } from \"react\"\nimport { StoreApi, useStore } from \"zustand\"\nimport { FlagmintStoreContext } from \"@/providers/FlagmintProvider\"\nimport { FlagStore, Flags } from \"@/types\"\n\n// Helper to get the store from context\nexport function useFlagmintStore(): StoreApi<FlagStore> {\n const store = useContext(FlagmintStoreContext)\n if (!store) {\n throw new Error('useFlagmintStore must be used within a FlagmintProvider')\n }\n return store\n}\n\n// Hook to get the client instance and update context\nexport function useFlagmint() {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n\n const updateContext = async (context: Record<string, any>) => {\n if (!client) {\n throw new Error('Flagmint client not initialized')\n }\n await client.updateContext(context)\n }\n\n return {\n client,\n isInitialized,\n updateContext\n }\n}\n\n// Hook to get all flags - only re-renders when flags change\nexport function useFlags(): Flags {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags)\n}\n\n// Simplified useFlag - just read from the reactive store\nexport function useFlag<T = FlagValue>(\n key: string,\n fallback?: T\n): T {\n const store = useFlagmintStore()\n \n // Subscribe to this specific flag in the store\n const flagValue = useStore(store, (state) => state.flags[key])\n \n // Return the flag value or fallback\n return (flagValue ?? fallback) as T\n}\n\n// Hook to check if the client is ready\nexport function useFlagmintReady(): boolean {\n const store = useFlagmintStore()\n \n // Subscribe to both isReady and isInitialized\n const isReady = useStore(store, (state) => state.isReady)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n \n return isReady && isInitialized\n}"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -26,6 +26,8 @@ interface FlagStore {
|
|
|
26
26
|
}
|
|
27
27
|
interface FlagmintProviderProps {
|
|
28
28
|
children: ReactNode;
|
|
29
|
+
createClient?: () => CoreClientLike;
|
|
30
|
+
client: CoreClientLike;
|
|
29
31
|
initialFlags?: Flags;
|
|
30
32
|
}
|
|
31
33
|
interface FlagmintProviderProps<T = unknown, C extends Record<string, any> = Record<string, any>> {
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ interface FlagStore {
|
|
|
26
26
|
}
|
|
27
27
|
interface FlagmintProviderProps {
|
|
28
28
|
children: ReactNode;
|
|
29
|
+
createClient?: () => CoreClientLike;
|
|
30
|
+
client: CoreClientLike;
|
|
29
31
|
initialFlags?: Flags;
|
|
30
32
|
}
|
|
31
33
|
interface FlagmintProviderProps<T = unknown, C extends Record<string, any> = Record<string, any>> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagmint-react-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "React wrapper for flagmint-js-sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"zustand": "^4.4.0",
|
|
35
|
-
"flagmint-js-sdk": "
|
|
35
|
+
"flagmint-js-sdk": "1.0.11"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"tsup": "^8.0.0",
|
package/README.md
DELETED
|
@@ -1,609 +0,0 @@
|
|
|
1
|
-
# flagmint-react-sdk
|
|
2
|
-
|
|
3
|
-
A React wrapper for [flagmint-js-sdk](https://www.npmjs.com/package/flagmint-js-sdk) that provides React-specific hooks and context with fine-grained reactivity powered by Zustand.
|
|
4
|
-
|
|
5
|
-
## ✨ Features
|
|
6
|
-
|
|
7
|
-
- 🎯 **Fine-grained reactivity** - Only components using specific flags re-render when those flags change
|
|
8
|
-
- 🚀 **SSR-safe** - No global state leakage between server requests
|
|
9
|
-
- 📦 **TypeScript support** - Full type safety with generics for flag values and context
|
|
10
|
-
- 🔀 **Dual export strategy** - Separate server-safe types and client-only hooks
|
|
11
|
-
- ⚡ **Auto-initialization** - Direct integration with FlagClient from flagmint-js-sdk
|
|
12
|
-
- 🔐 **Deferred initialization** - Perfect for authentication flows
|
|
13
|
-
- 📱 **Framework agnostic** - Works with Next.js, Remix, Vite, and more
|
|
14
|
-
|
|
15
|
-
## 📦 Installation
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install react-flagmint flagmint-js-sdk
|
|
19
|
-
# or
|
|
20
|
-
pnpm add react-flagmint flagmint-js-sdk
|
|
21
|
-
# or
|
|
22
|
-
yarn add react-flagmint flagmint-js-sdk
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## 🚀 Quick Start
|
|
26
|
-
|
|
27
|
-
### 1. Basic Setup
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
// app/providers.tsx (Next.js App Router)
|
|
31
|
-
'use client'
|
|
32
|
-
import { FlagmintProvider } from 'react-flagmint/client'
|
|
33
|
-
|
|
34
|
-
export default function Providers({ children }: { children: React.ReactNode }) {
|
|
35
|
-
return (
|
|
36
|
-
<FlagmintProvider
|
|
37
|
-
options={{
|
|
38
|
-
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
39
|
-
transportMode: 'websocket',
|
|
40
|
-
context: {
|
|
41
|
-
userId: 'user-123',
|
|
42
|
-
plan: 'premium'
|
|
43
|
-
}
|
|
44
|
-
}}
|
|
45
|
-
>
|
|
46
|
-
{children}
|
|
47
|
-
</FlagmintProvider>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
// app/layout.tsx
|
|
54
|
-
import Providers from './providers'
|
|
55
|
-
|
|
56
|
-
export default function RootLayout({
|
|
57
|
-
children,
|
|
58
|
-
}: {
|
|
59
|
-
children: React.ReactNode
|
|
60
|
-
}) {
|
|
61
|
-
return (
|
|
62
|
-
<html>
|
|
63
|
-
<body>
|
|
64
|
-
<Providers>
|
|
65
|
-
{children}
|
|
66
|
-
</Providers>
|
|
67
|
-
</body>
|
|
68
|
-
</html>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### 2. Using Flags in Components
|
|
74
|
-
|
|
75
|
-
```tsx
|
|
76
|
-
// components/FeatureComponent.tsx
|
|
77
|
-
'use client'
|
|
78
|
-
import { useFlag, useFlags, useFlagmintReady } from 'react-flagmint/client'
|
|
79
|
-
|
|
80
|
-
export default function FeatureComponent() {
|
|
81
|
-
const showNewFeature = useFlag('new-dashboard', false)
|
|
82
|
-
const userPlan = useFlag('user-plan', 'free')
|
|
83
|
-
const isReady = useFlagmintReady()
|
|
84
|
-
|
|
85
|
-
if (!isReady) {
|
|
86
|
-
return <div>Loading flags...</div>
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<div>
|
|
91
|
-
{showNewFeature && <NewDashboard />}
|
|
92
|
-
<div>Current plan: {userPlan}</div>
|
|
93
|
-
</div>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## 📚 API Reference
|
|
99
|
-
|
|
100
|
-
### FlagmintProvider
|
|
101
|
-
|
|
102
|
-
The provider component that initializes the FlagClient and provides flag context to your app.
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
<FlagmintProvider
|
|
106
|
-
options={FlagClientOptions}
|
|
107
|
-
initialFlags={{}}
|
|
108
|
-
deferInitialization={false}
|
|
109
|
-
>
|
|
110
|
-
{children}
|
|
111
|
-
</FlagmintProvider>
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
#### Props
|
|
115
|
-
|
|
116
|
-
| Prop | Type | Default | Description |
|
|
117
|
-
|------|------|---------|-------------|
|
|
118
|
-
| `options` | `FlagClientOptions` | Required | Configuration for the FlagClient |
|
|
119
|
-
| `initialFlags` | `Flags` | `{}` | Initial flag values for SSR/hydration |
|
|
120
|
-
| `deferInitialization` | `boolean` | `false` | If true, wait for manual initialization |
|
|
121
|
-
|
|
122
|
-
#### FlagClientOptions
|
|
123
|
-
|
|
124
|
-
```tsx
|
|
125
|
-
interface FlagClientOptions<C extends Record<string, any> = Record<string, any>> {
|
|
126
|
-
apiKey: string
|
|
127
|
-
context?: C
|
|
128
|
-
transportMode?: 'auto' | 'websocket' | 'long-polling'
|
|
129
|
-
enableOfflineCache?: boolean
|
|
130
|
-
persistContext?: boolean
|
|
131
|
-
onError?: (error: Error) => void
|
|
132
|
-
previewMode?: boolean
|
|
133
|
-
rawFlags?: Record<string, FlagValue>
|
|
134
|
-
deferInitialization?: boolean
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Hooks
|
|
139
|
-
|
|
140
|
-
#### useFlag(key, fallback?)
|
|
141
|
-
|
|
142
|
-
Get a specific flag value with fine-grained reactivity.
|
|
143
|
-
|
|
144
|
-
```tsx
|
|
145
|
-
const showFeature = useFlag('feature-name', false)
|
|
146
|
-
const userTier = useFlag('user-tier', 'free')
|
|
147
|
-
const config = useFlag('app-config', { theme: 'light' })
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**Parameters:**
|
|
151
|
-
- `key: string` - The flag key
|
|
152
|
-
- `fallback?: T` - Default value if flag is not found
|
|
153
|
-
|
|
154
|
-
**Returns:** The flag value or fallback
|
|
155
|
-
|
|
156
|
-
#### useFlags()
|
|
157
|
-
|
|
158
|
-
Get all currently loaded flags.
|
|
159
|
-
|
|
160
|
-
```tsx
|
|
161
|
-
const allFlags = useFlags()
|
|
162
|
-
console.log(allFlags) // { 'feature-a': true, 'user-tier': 'pro' }
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**Returns:** Object containing all loaded flags
|
|
166
|
-
|
|
167
|
-
#### useFlagmint()
|
|
168
|
-
|
|
169
|
-
Get access to the FlagClient instance and utilities.
|
|
170
|
-
|
|
171
|
-
```tsx
|
|
172
|
-
const { client, isReady, isInitialized, updateContext } = useFlagmint()
|
|
173
|
-
|
|
174
|
-
// Update user context
|
|
175
|
-
await updateContext({ userId: 'new-user', plan: 'enterprise' })
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
**Returns:**
|
|
179
|
-
```tsx
|
|
180
|
-
{
|
|
181
|
-
client: FlagClient | null
|
|
182
|
-
isReady: boolean
|
|
183
|
-
isInitialized: boolean
|
|
184
|
-
updateContext: (context: Record<string, any>) => Promise<void>
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
#### useFlagmintReady()
|
|
189
|
-
|
|
190
|
-
Check if the FlagClient is ready to serve flags.
|
|
191
|
-
|
|
192
|
-
```tsx
|
|
193
|
-
const isReady = useFlagmintReady()
|
|
194
|
-
|
|
195
|
-
if (!isReady) {
|
|
196
|
-
return <LoadingSpinner />
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
#### useFlagClient()
|
|
201
|
-
|
|
202
|
-
Get direct access to the FlagClient instance.
|
|
203
|
-
|
|
204
|
-
```tsx
|
|
205
|
-
const client = useFlagClient<MyFlagTypes>()
|
|
206
|
-
const specificFlag = client?.getFlag('feature-x', false)
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## 🔧 Usage Patterns
|
|
210
|
-
|
|
211
|
-
### 1. Auto-initialization (Default)
|
|
212
|
-
|
|
213
|
-
Best when user context is available immediately:
|
|
214
|
-
|
|
215
|
-
```tsx
|
|
216
|
-
function App() {
|
|
217
|
-
return (
|
|
218
|
-
<FlagmintProvider
|
|
219
|
-
options={{
|
|
220
|
-
apiKey: 'your-api-key',
|
|
221
|
-
context: {
|
|
222
|
-
userId: getCurrentUser().id,
|
|
223
|
-
plan: getCurrentUser().plan
|
|
224
|
-
}
|
|
225
|
-
}}
|
|
226
|
-
>
|
|
227
|
-
<Dashboard />
|
|
228
|
-
</FlagmintProvider>
|
|
229
|
-
)
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### 2. Deferred Initialization
|
|
234
|
-
|
|
235
|
-
Perfect for authentication flows:
|
|
236
|
-
|
|
237
|
-
```tsx
|
|
238
|
-
// Provider with deferred initialization
|
|
239
|
-
<FlagmintProvider
|
|
240
|
-
options={{
|
|
241
|
-
apiKey: 'your-api-key',
|
|
242
|
-
context: {} // Empty initially
|
|
243
|
-
}}
|
|
244
|
-
deferInitialization={true}
|
|
245
|
-
>
|
|
246
|
-
<App />
|
|
247
|
-
</FlagmintProvider>
|
|
248
|
-
|
|
249
|
-
// Login component
|
|
250
|
-
function LoginPage() {
|
|
251
|
-
const { updateContext } = useFlagmint()
|
|
252
|
-
|
|
253
|
-
const handleLogin = async (user) => {
|
|
254
|
-
// First update context with user info
|
|
255
|
-
await updateContext({
|
|
256
|
-
userId: user.id,
|
|
257
|
-
plan: user.plan,
|
|
258
|
-
locale: user.locale
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
// Flags are now available with user context
|
|
262
|
-
navigate('/dashboard')
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return <LoginForm onLogin={handleLogin} />
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### 3. TypeScript Usage
|
|
270
|
-
|
|
271
|
-
Define your flag types for better type safety:
|
|
272
|
-
|
|
273
|
-
```tsx
|
|
274
|
-
// types/flags.ts
|
|
275
|
-
export interface AppFlags {
|
|
276
|
-
'show-beta-feature': boolean
|
|
277
|
-
'user-plan': 'free' | 'pro' | 'enterprise'
|
|
278
|
-
'theme-config': {
|
|
279
|
-
primaryColor: string
|
|
280
|
-
darkMode: boolean
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export interface UserContext {
|
|
285
|
-
userId: string
|
|
286
|
-
plan: string
|
|
287
|
-
locale: string
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// components/TypedComponent.tsx
|
|
291
|
-
import { useFlag } from 'react-flagmint/client'
|
|
292
|
-
import type { AppFlags } from '../types/flags'
|
|
293
|
-
|
|
294
|
-
export default function TypedComponent() {
|
|
295
|
-
// TypeScript knows this returns boolean
|
|
296
|
-
const showBeta = useFlag<AppFlags['show-beta-feature']>('show-beta-feature', false)
|
|
297
|
-
|
|
298
|
-
// TypeScript knows this returns the union type
|
|
299
|
-
const plan = useFlag<AppFlags['user-plan']>('user-plan', 'free')
|
|
300
|
-
|
|
301
|
-
return (
|
|
302
|
-
<div>
|
|
303
|
-
{showBeta && <BetaFeature />}
|
|
304
|
-
<div>Plan: {plan}</div>
|
|
305
|
-
</div>
|
|
306
|
-
)
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### 4. Server-Side Rendering (SSR)
|
|
311
|
-
|
|
312
|
-
#### Next.js App Router
|
|
313
|
-
|
|
314
|
-
```tsx
|
|
315
|
-
// app/providers.tsx
|
|
316
|
-
'use client'
|
|
317
|
-
import { FlagmintProvider } from 'react-flagmint/client'
|
|
318
|
-
|
|
319
|
-
export default function Providers({ children, initialFlags = {} }) {
|
|
320
|
-
return (
|
|
321
|
-
<FlagmintProvider
|
|
322
|
-
options={{
|
|
323
|
-
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
324
|
-
transportMode: 'websocket'
|
|
325
|
-
}}
|
|
326
|
-
initialFlags={initialFlags}
|
|
327
|
-
>
|
|
328
|
-
{children}
|
|
329
|
-
</FlagmintProvider>
|
|
330
|
-
)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// app/page.tsx
|
|
334
|
-
import { FlagClient } from 'flagmint-js-sdk'
|
|
335
|
-
import Providers from './providers'
|
|
336
|
-
import ClientComponent from './ClientComponent'
|
|
337
|
-
|
|
338
|
-
export default async function Page() {
|
|
339
|
-
// Optionally preload flags on server
|
|
340
|
-
let initialFlags = {}
|
|
341
|
-
|
|
342
|
-
try {
|
|
343
|
-
const serverClient = new FlagClient({
|
|
344
|
-
apiKey: process.env.FLAGMINT_API_KEY!,
|
|
345
|
-
context: { server: true }
|
|
346
|
-
})
|
|
347
|
-
await serverClient.ready()
|
|
348
|
-
initialFlags = await serverClient.getFlags() // If this method exists
|
|
349
|
-
} catch (error) {
|
|
350
|
-
console.warn('Failed to load initial flags:', error)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return (
|
|
354
|
-
<Providers initialFlags={initialFlags}>
|
|
355
|
-
<ClientComponent />
|
|
356
|
-
</Providers>
|
|
357
|
-
)
|
|
358
|
-
}
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### 5. Context Updates
|
|
362
|
-
|
|
363
|
-
Update user context dynamically:
|
|
364
|
-
|
|
365
|
-
```tsx
|
|
366
|
-
function UserSettings() {
|
|
367
|
-
const { updateContext } = useFlagmint()
|
|
368
|
-
const currentTheme = useFlag('user-theme', 'light')
|
|
369
|
-
|
|
370
|
-
const handleThemeChange = async (newTheme: string) => {
|
|
371
|
-
// Update context - flags will be re-evaluated
|
|
372
|
-
await updateContext({ theme: newTheme })
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const handlePlanUpgrade = async (newPlan: string) => {
|
|
376
|
-
await updateContext({ plan: newPlan })
|
|
377
|
-
// Component will re-render with new plan-based flags
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return (
|
|
381
|
-
<div>
|
|
382
|
-
<ThemeSelector onChange={handleThemeChange} />
|
|
383
|
-
<PlanUpgrade onUpgrade={handlePlanUpgrade} />
|
|
384
|
-
</div>
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
### 6. Loading States
|
|
390
|
-
|
|
391
|
-
Handle loading states gracefully:
|
|
392
|
-
|
|
393
|
-
```tsx
|
|
394
|
-
function FeaturePage() {
|
|
395
|
-
const isReady = useFlagmintReady()
|
|
396
|
-
const showPremiumFeature = useFlag('premium-feature', false)
|
|
397
|
-
|
|
398
|
-
if (!isReady) {
|
|
399
|
-
return (
|
|
400
|
-
<div className="loading-container">
|
|
401
|
-
<Spinner />
|
|
402
|
-
<p>Loading personalized features...</p>
|
|
403
|
-
</div>
|
|
404
|
-
)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return (
|
|
408
|
-
<div>
|
|
409
|
-
<h1>Features</h1>
|
|
410
|
-
{showPremiumFeature ? (
|
|
411
|
-
<PremiumFeature />
|
|
412
|
-
) : (
|
|
413
|
-
<UpgradeBanner />
|
|
414
|
-
)}
|
|
415
|
-
</div>
|
|
416
|
-
)
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### 7. Error Handling
|
|
421
|
-
|
|
422
|
-
```tsx
|
|
423
|
-
<FlagmintProvider
|
|
424
|
-
options={{
|
|
425
|
-
apiKey: 'your-api-key',
|
|
426
|
-
onError: (error) => {
|
|
427
|
-
console.error('Flagmint error:', error)
|
|
428
|
-
// Send to error reporting service
|
|
429
|
-
errorReporting.captureException(error)
|
|
430
|
-
}
|
|
431
|
-
}}
|
|
432
|
-
>
|
|
433
|
-
<App />
|
|
434
|
-
</FlagmintProvider>
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
## 🏗️ Framework Examples
|
|
438
|
-
|
|
439
|
-
### Next.js (App Router)
|
|
440
|
-
|
|
441
|
-
```tsx
|
|
442
|
-
// app/layout.tsx
|
|
443
|
-
import Providers from './providers'
|
|
444
|
-
|
|
445
|
-
export default function RootLayout({ children }) {
|
|
446
|
-
return (
|
|
447
|
-
<html lang="en">
|
|
448
|
-
<body>
|
|
449
|
-
<Providers>{children}</Providers>
|
|
450
|
-
</body>
|
|
451
|
-
</html>
|
|
452
|
-
)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// app/providers.tsx
|
|
456
|
-
'use client'
|
|
457
|
-
import { FlagmintProvider } from 'react-flagmint/client'
|
|
458
|
-
|
|
459
|
-
export default function Providers({ children }) {
|
|
460
|
-
return (
|
|
461
|
-
<FlagmintProvider
|
|
462
|
-
options={{
|
|
463
|
-
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
464
|
-
transportMode: 'long-polling'
|
|
465
|
-
}}
|
|
466
|
-
>
|
|
467
|
-
{children}
|
|
468
|
-
</FlagmintProvider>
|
|
469
|
-
)
|
|
470
|
-
}
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### Vite + React
|
|
474
|
-
|
|
475
|
-
```tsx
|
|
476
|
-
// src/main.tsx
|
|
477
|
-
import React from 'react'
|
|
478
|
-
import ReactDOM from 'react-dom/client'
|
|
479
|
-
import { FlagmintProvider } from 'react-flagmint/client'
|
|
480
|
-
import App from './App'
|
|
481
|
-
|
|
482
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
483
|
-
<React.StrictMode>
|
|
484
|
-
<FlagmintProvider
|
|
485
|
-
options={{
|
|
486
|
-
apiKey: import.meta.env.VITE_FLAGMINT_API_KEY,
|
|
487
|
-
transportMode: 'websocket'
|
|
488
|
-
}}
|
|
489
|
-
>
|
|
490
|
-
<App />
|
|
491
|
-
</FlagmintProvider>
|
|
492
|
-
</React.StrictMode>
|
|
493
|
-
)
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
### Remix
|
|
497
|
-
|
|
498
|
-
```tsx
|
|
499
|
-
// app/root.tsx
|
|
500
|
-
import { FlagmintProvider } from 'react-flagmint/client'
|
|
501
|
-
|
|
502
|
-
export default function App() {
|
|
503
|
-
return (
|
|
504
|
-
<html>
|
|
505
|
-
<head>
|
|
506
|
-
<Meta />
|
|
507
|
-
<Links />
|
|
508
|
-
</head>
|
|
509
|
-
<body>
|
|
510
|
-
<FlagmintProvider
|
|
511
|
-
options={{
|
|
512
|
-
apiKey: process.env.FLAGMINT_API_KEY!,
|
|
513
|
-
transportMode: 'long-polling'
|
|
514
|
-
}}
|
|
515
|
-
>
|
|
516
|
-
<Outlet />
|
|
517
|
-
</FlagmintProvider>
|
|
518
|
-
<Scripts />
|
|
519
|
-
</body>
|
|
520
|
-
</html>
|
|
521
|
-
)
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
## ⚡ Performance
|
|
526
|
-
|
|
527
|
-
### Fine-grained Reactivity
|
|
528
|
-
|
|
529
|
-
Only components that use specific flags will re-render when those flags change:
|
|
530
|
-
|
|
531
|
-
```tsx
|
|
532
|
-
// ✅ Good: Only re-renders when 'feature-a' changes
|
|
533
|
-
function ComponentA() {
|
|
534
|
-
const featureA = useFlag('feature-a', false)
|
|
535
|
-
return <div>{featureA && <FeatureA />}</div>
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// ✅ Good: Only re-renders when 'feature-b' changes
|
|
539
|
-
function ComponentB() {
|
|
540
|
-
const featureB = useFlag('feature-b', false)
|
|
541
|
-
return <div>{featureB && <FeatureB />}</div>
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// ❌ Avoid: Re-renders when ANY flag changes
|
|
545
|
-
function ComponentAll() {
|
|
546
|
-
const allFlags = useFlags()
|
|
547
|
-
return <div>{allFlags['feature-a'] && <FeatureA />}</div>
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### Bundle Size
|
|
552
|
-
|
|
553
|
-
- **react-flagmint**: ~3KB gzipped
|
|
554
|
-
- **zustand**: ~2KB gzipped
|
|
555
|
-
- **Total overhead**: ~5KB gzipped
|
|
556
|
-
|
|
557
|
-
## 🔧 Development
|
|
558
|
-
|
|
559
|
-
### Local Development
|
|
560
|
-
|
|
561
|
-
```bash
|
|
562
|
-
# Clone the monorepo
|
|
563
|
-
git clone <repo-url>
|
|
564
|
-
cd flagmint-monorepo
|
|
565
|
-
|
|
566
|
-
# Install dependencies
|
|
567
|
-
pnpm install
|
|
568
|
-
|
|
569
|
-
# Build the library
|
|
570
|
-
pnpm --filter react-flagmint build
|
|
571
|
-
|
|
572
|
-
# Run the Next.js example
|
|
573
|
-
pnpm dev:next
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### Testing
|
|
577
|
-
|
|
578
|
-
```bash
|
|
579
|
-
# Run tests
|
|
580
|
-
pnpm test
|
|
581
|
-
|
|
582
|
-
# Type checking
|
|
583
|
-
pnpm typecheck
|
|
584
|
-
|
|
585
|
-
# Lint
|
|
586
|
-
pnpm lint
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
## 📄 License
|
|
590
|
-
|
|
591
|
-
MIT
|
|
592
|
-
|
|
593
|
-
## 🤝 Contributing
|
|
594
|
-
|
|
595
|
-
1. Fork the repository
|
|
596
|
-
2. Create a feature branch
|
|
597
|
-
3. Make your changes
|
|
598
|
-
4. Add tests
|
|
599
|
-
5. Submit a pull request
|
|
600
|
-
|
|
601
|
-
## 📞 Support
|
|
602
|
-
|
|
603
|
-
- 📧 Email: support@flagmint.com
|
|
604
|
-
- 🐛 Issues: [GitHub Issues](https://github.com/jtad009/flagmint-react-sdk/issues)
|
|
605
|
-
- 📖 Docs: [Documentation](https://docs.flagmint.com)
|
|
606
|
-
|
|
607
|
-
---
|
|
608
|
-
|
|
609
|
-
**Made with ❤️ by the Flagmint team**
|