From 05b0651235b5f88ffc78658666a5e65adb58393f Mon Sep 17 00:00:00 2001 From: ayip8 <16192761+ayip8@users.noreply.github.com> Date: Fri, 17 May 2024 17:27:40 -0400 Subject: [PATCH 1/9] WIP --- src/FlagToggleOverlay.tsx | 50 +++++++++++++++++++++++++++++++++++++++ src/index.tsx | 43 +++++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/FlagToggleOverlay.tsx diff --git a/src/FlagToggleOverlay.tsx b/src/FlagToggleOverlay.tsx new file mode 100644 index 0000000..2b2ba19 --- /dev/null +++ b/src/FlagToggleOverlay.tsx @@ -0,0 +1,50 @@ +import React, { useEffect, useState } from "react"; +import { ConfigValue } from "@prefab-cloud/prefab-cloud-js"; + +interface Props { + requestedFlags: () => { key: string; value: ConfigValue }[]; + overrides: Map; + addOverride: (key: string, value: ConfigValue) => void; +} + +function FlagToggleOverlay({ requestedFlags, overrides, addOverride }: Props) { + const [showOverlay, setShowOverlay] = useState(false); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.ctrlKey && event.key === "f") { + setShowOverlay((prev) => !prev); + console.log("Toggled overlay:", !showOverlay); + } + }; + window.addEventListener("keydown", handleKeyDown); + + return () => window.removeEventListener("keydown", handleKeyDown); + }, []); + + if (!showOverlay) return null; + + return ( +
+

Feature Flags

+ +
+ ); +} + +export default FlagToggleOverlay; diff --git a/src/index.tsx b/src/index.tsx index c77ee38..8895bbc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,7 @@ import React, { PropsWithChildren } from "react"; import { prefab, ConfigValue, Context } from "@prefab-cloud/prefab-cloud-js"; import version from "./version"; +import FlagToggleOverlay from "./FlagToggleOverlay"; type ContextValue = number | string | boolean; type ContextAttributes = { [key: string]: Record }; @@ -40,6 +41,7 @@ type Props = { afterEvaluationCallback?: EvaluationCallback; collectEvaluationSummaries?: boolean; collectLoggerNames?: boolean; + allowTogglingUi?: boolean; }; function PrefabProvider({ @@ -54,6 +56,7 @@ function PrefabProvider({ afterEvaluationCallback = undefined, collectEvaluationSummaries = false, collectLoggerNames = false, + allowTogglingUi = false, }: PropsWithChildren) { // We use this state to prevent a double-init when useEffect fires due to // StrictMode @@ -64,6 +67,8 @@ function PrefabProvider({ // Here we track the current identity so we can reload our config when it // changes const [loadedContextKey, setLoadedContextKey] = React.useState(""); + // Here we store overrides for the flag toggling UI + const [overrides, setOverrides] = React.useState>(new Map()); if (Object.keys(contextAttributes).length === 0) { // eslint-disable-next-line no-console @@ -129,17 +134,47 @@ function PrefabProvider({ const value: ProvidedContext = React.useMemo( () => ({ - isEnabled: prefab.isEnabled.bind(prefab), + isEnabled: (key: string) => { + console.log("isEnabled: ", key); + if (overrides.has(key)) { + return overrides.get(key) as boolean; + } + return prefab.isEnabled.bind(prefab)(key); + }, contextAttributes, - get: prefab.get.bind(prefab), + get: (key: string) => { + console.log("get: ", key); + if (overrides.has(key)) { + return overrides.get(key); + } + return prefab.get.bind(prefab)(key); + }, keys: Object.keys(prefab.configs), prefab, loading, }), - [loadedContextKey, loading, prefab] + [loadedContextKey, loading, prefab, overrides] ); - return {children}; + const requestedFlags = () => prefab.requestedFlags.bind(prefab)(); + + return ( + + {children} + {allowTogglingUi && !loading && ( + { + console.log("addOverride: ", key, newValue); + const newOverrides = new Map(overrides); + newOverrides.set(key, newValue); + setOverrides(newOverrides); + }} + /> + )} + + ); } type TestProps = { From 1b014fab72ff12708e7e9d5bbd9b1ed94c973f2d Mon Sep 17 00:00:00 2001 From: ayip8 <16192761+ayip8@users.noreply.github.com> Date: Fri, 17 May 2024 18:01:03 -0400 Subject: [PATCH 2/9] WIP --- src/FlagToggleOverlay.tsx | 105 ++++++++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 17 deletions(-) diff --git a/src/FlagToggleOverlay.tsx b/src/FlagToggleOverlay.tsx index 2b2ba19..0043fb6 100644 --- a/src/FlagToggleOverlay.tsx +++ b/src/FlagToggleOverlay.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import { ConfigValue } from "@prefab-cloud/prefab-cloud-js"; +import { act } from "@testing-library/react"; interface Props { requestedFlags: () => { key: string; value: ConfigValue }[]; @@ -25,24 +26,94 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride }: Props) { if (!showOverlay) return null; return ( -
-

Feature Flags

-
    - {requestedFlags().map(({ key, value }) => ( -
  • - {key} - {overrides.has(key) ? overrides.get(key)?.toString() : value?.toString()} - { - addOverride(key, overrides.has(key) ? !overrides.get(key) : !value); - }} - className="form-checkbox h-6 w-6 text-indigo-600 transition duration-150 ease-in-out" +
    +
    + + + -
  • - ))} -
+ + + + + + + + + + + + +
+
+
Feature Flags
+
    + {requestedFlags().map(({ key, value }) => { + const actualValue = overrides.has(key) ? overrides.get(key) : value; + + return ( +
  • + {typeof actualValue === "boolean" ? ( + + ) : ( + <> + {key} + {actualValue?.toString()} + + )} +
  • + ); + })} +
+
); } From 2dd26692fc17959f5ab964e85f59857c77268a14 Mon Sep 17 00:00:00 2001 From: ayip8 <16192761+ayip8@users.noreply.github.com> Date: Fri, 17 May 2024 18:09:18 -0400 Subject: [PATCH 3/9] WIP --- src/FlagToggleOverlay.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FlagToggleOverlay.tsx b/src/FlagToggleOverlay.tsx index 0043fb6..99544da 100644 --- a/src/FlagToggleOverlay.tsx +++ b/src/FlagToggleOverlay.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from "react"; import { ConfigValue } from "@prefab-cloud/prefab-cloud-js"; -import { act } from "@testing-library/react"; interface Props { requestedFlags: () => { key: string; value: ConfigValue }[]; @@ -33,9 +32,10 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride }: Props) { right: "48px", borderColor: "#4352D1", boxShadow: "0px 0px 35px rgba(67,82,209,0.35)", + overflow: "hidden", }} > -
+
Date: Sat, 18 May 2024 10:32:24 -0400 Subject: [PATCH 4/9] WIP storing usage in a ref --- src/index.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 8895bbc..f5d8eb6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -77,6 +77,8 @@ function PrefabProvider({ ); } + const usageRef = React.useRef(new Map()); + const context = new Context(contextAttributes); const contextKey = context.encode(); @@ -135,19 +137,21 @@ function PrefabProvider({ const value: ProvidedContext = React.useMemo( () => ({ isEnabled: (key: string) => { - console.log("isEnabled: ", key); if (overrides.has(key)) { return overrides.get(key) as boolean; } - return prefab.isEnabled.bind(prefab)(key); + const result = prefab.isEnabled.bind(prefab)(key); + usageRef.current.set(key, result); + return result; }, contextAttributes, get: (key: string) => { - console.log("get: ", key); if (overrides.has(key)) { return overrides.get(key); } - return prefab.get.bind(prefab)(key); + const result = prefab.get.bind(prefab)(key); + usageRef.current.set(key, result); + return result; }, keys: Object.keys(prefab.configs), prefab, @@ -156,7 +160,12 @@ function PrefabProvider({ [loadedContextKey, loading, prefab, overrides] ); - const requestedFlags = () => prefab.requestedFlags.bind(prefab)(); + // const requestedFlags = () => prefab.requestedFlags.bind(prefab)(); + const requestedFlags = () => + Array.from(usageRef.current.entries()).map(([key, val]) => ({ + key, + value: val, + })); return ( From f89e51802b7cfa860abe38f02da2cdbe81e739a6 Mon Sep 17 00:00:00 2001 From: ayip8 <16192761+ayip8@users.noreply.github.com> Date: Sat, 18 May 2024 11:04:16 -0400 Subject: [PATCH 5/9] WIP handle page changes, poll for flags --- src/FlagToggleOverlay.tsx | 69 +++++++++++++++++++++++++++++++++++++-- src/index.tsx | 13 +++++--- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/FlagToggleOverlay.tsx b/src/FlagToggleOverlay.tsx index 99544da..b0ebfd0 100644 --- a/src/FlagToggleOverlay.tsx +++ b/src/FlagToggleOverlay.tsx @@ -5,11 +5,24 @@ interface Props { requestedFlags: () => { key: string; value: ConfigValue }[]; overrides: Map; addOverride: (key: string, value: ConfigValue) => void; + clearFlags: () => void; } -function FlagToggleOverlay({ requestedFlags, overrides, addOverride }: Props) { +function FlagToggleOverlay({ requestedFlags, overrides, addOverride, clearFlags }: Props) { const [showOverlay, setShowOverlay] = useState(false); + // const [currentPage, setCurrentPage] = useState(window.location.pathname); + // poll for flags + const [flags, setFlags] = useState(requestedFlags()); + useEffect(() => { + const interval = setInterval(() => { + setFlags(requestedFlags()); + }, 1000); + + return () => clearInterval(interval); + }, []); + + // setup keyboard shortcut listener to toggle overlay useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.ctrlKey && event.key === "f") { @@ -22,6 +35,58 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride }: Props) { return () => window.removeEventListener("keydown", handleKeyDown); }, []); + // setup listeners for page changes + React.useEffect(() => { + const handlePopState = () => { + clearFlags(); + setCurrentPage(window.location.pathname); + }; + + const patchHistoryMethods = () => { + const originalPushState = window.history.pushState; + const originalReplaceState = window.history.replaceState; + + window.history.pushState = function ( + state: any, + unused: string, + url?: string | URL | null | undefined + ) { + const result = originalPushState.apply(window.history, [state, unused, url]); + window.dispatchEvent(new Event("pushState")); + return result; + }; + + window.history.replaceState = function ( + state: any, + unused: string, + url?: string | URL | null | undefined + ) { + const result = originalReplaceState.apply(window.history, [state, unused, url]); + window.dispatchEvent(new Event("replaceState")); + return result; + }; + + window.addEventListener("pushState", handlePopState); + window.addEventListener("replaceState", handlePopState); + + return () => { + window.removeEventListener("pushState", handlePopState); + window.removeEventListener("replaceState", handlePopState); + window.history.pushState = originalPushState; + window.history.replaceState = originalReplaceState; + }; + }; + + const unpatchHistoryMethods = patchHistoryMethods(); + + window.addEventListener("popstate", handlePopState); + + return () => { + window.removeEventListener("popstate", handlePopState); + unpatchHistoryMethods(); + }; + }, []); + if (!showOverlay) return null; return ( @@ -80,7 +145,7 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride }: Props) {
Feature Flags
    - {requestedFlags().map(({ key, value }) => { + {flags.map(({ key, value }) => { const actualValue = overrides.has(key) ? overrides.get(key) : value; return ( diff --git a/src/index.tsx b/src/index.tsx index f5d8eb6..9ebf811 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -162,10 +162,12 @@ function PrefabProvider({ // const requestedFlags = () => prefab.requestedFlags.bind(prefab)(); const requestedFlags = () => - Array.from(usageRef.current.entries()).map(([key, val]) => ({ - key, - value: val, - })); + Array.from(usageRef.current.entries()) + .map(([key, val]) => ({ + key, + value: val, + })) + .sort((a, b) => a.key.localeCompare(b.key)); return ( @@ -180,6 +182,9 @@ function PrefabProvider({ newOverrides.set(key, newValue); setOverrides(newOverrides); }} + clearFlags={() => { + usageRef.current.clear(); + }} /> )} From 0e1cf8130d555c2916d04a8e7f55792b4dfbd65f Mon Sep 17 00:00:00 2001 From: ayip8 <16192761+ayip8@users.noreply.github.com> Date: Sat, 18 May 2024 12:37:31 -0400 Subject: [PATCH 6/9] WIP --- src/FlagToggleOverlay.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FlagToggleOverlay.tsx b/src/FlagToggleOverlay.tsx index b0ebfd0..b3c93af 100644 --- a/src/FlagToggleOverlay.tsx +++ b/src/FlagToggleOverlay.tsx @@ -13,6 +13,7 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride, clearFlags // const [currentPage, setCurrentPage] = useState(window.location.pathname); // poll for flags + // TODO turn this on and off based on whether the overlay is visible const [flags, setFlags] = useState(requestedFlags()); useEffect(() => { const interval = setInterval(() => { From f654be88bb0d22c35c5ddd8a380472a0e091a85c Mon Sep 17 00:00:00 2001 From: ayip8 <16192761+ayip8@users.noreply.github.com> Date: Mon, 20 May 2024 17:10:48 -0400 Subject: [PATCH 7/9] Add todo --- src/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.tsx b/src/index.tsx index 9ebf811..a246ec1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -172,6 +172,7 @@ function PrefabProvider({ return ( {children} + {/* TODO: not sure this actually has to block on loading any more */} {allowTogglingUi && !loading && ( Date: Thu, 23 May 2024 10:15:23 -0400 Subject: [PATCH 8/9] Make the overlay a provider --- src/FlagToggleOverlay.tsx | 248 ++++++++++++++++++++++---------------- src/index.tsx | 85 ++----------- src/types.ts | 26 ++++ 3 files changed, 177 insertions(+), 182 deletions(-) create mode 100644 src/types.ts diff --git a/src/FlagToggleOverlay.tsx b/src/FlagToggleOverlay.tsx index b3c93af..e4aeea8 100644 --- a/src/FlagToggleOverlay.tsx +++ b/src/FlagToggleOverlay.tsx @@ -1,23 +1,59 @@ -import React, { useEffect, useState } from "react"; +import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { ConfigValue } from "@prefab-cloud/prefab-cloud-js"; +import { PrefabContext, ProvidedContext } from "./types"; -interface Props { - requestedFlags: () => { key: string; value: ConfigValue }[]; - overrides: Map; - addOverride: (key: string, value: ConfigValue) => void; - clearFlags: () => void; -} - -function FlagToggleOverlay({ requestedFlags, overrides, addOverride, clearFlags }: Props) { +function FlagToggleOverlay({ children }: { children: React.ReactNode | undefined }) { + const parentContext: ProvidedContext = useContext(PrefabContext); const [showOverlay, setShowOverlay] = useState(false); - // const [currentPage, setCurrentPage] = useState(window.location.pathname); + const [flags, setFlags] = useState<{ key: string; value: ConfigValue }[]>([]); + const [overrides, setOverrides] = useState>(new Map()); + + const usageRef = useRef(new Map()); + + const addOverride = (key: string, value: ConfigValue) => { + setOverrides((prevOverrides) => { + const newOverrides = new Map(prevOverrides); + newOverrides.set(key, value); + return newOverrides; + }); + }; + + const clearFlags = () => { + usageRef.current.clear(); + }; + + const contextValue = useMemo( + () => ({ + ...parentContext, + get: (key: string) => { + if (overrides.has(key)) { + const result = overrides.get(key); + usageRef.current.set(key, result); + return result; + } + const result = parentContext.get(key); + usageRef.current.set(key, result); + return result; + }, + isEnabled: (key: string) => { + if (overrides.has(key)) { + const result = overrides.get(key) as boolean; + usageRef.current.set(key, result); + return result; + } + const result = parentContext.isEnabled(key); + usageRef.current.set(key, result); + return result; + }, + }), + [parentContext, overrides] + ); // poll for flags // TODO turn this on and off based on whether the overlay is visible - const [flags, setFlags] = useState(requestedFlags()); useEffect(() => { const interval = setInterval(() => { - setFlags(requestedFlags()); + setFlags(Array.from(usageRef.current.entries()).map(([key, value]) => ({ key, value }))); }, 1000); return () => clearInterval(interval); @@ -28,7 +64,6 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride, clearFlags const handleKeyDown = (event: KeyboardEvent) => { if (event.ctrlKey && event.key === "f") { setShowOverlay((prev) => !prev); - console.log("Toggled overlay:", !showOverlay); } }; window.addEventListener("keydown", handleKeyDown); @@ -37,10 +72,9 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride, clearFlags }, []); // setup listeners for page changes - React.useEffect(() => { + useEffect(() => { const handlePopState = () => { clearFlags(); - setCurrentPage(window.location.pathname); }; const patchHistoryMethods = () => { @@ -88,99 +122,103 @@ function FlagToggleOverlay({ requestedFlags, overrides, addOverride, clearFlags }; }, []); - if (!showOverlay) return null; + if (!showOverlay) + return {children}; return ( -
    -
    - - - - - - - - - - - - - - - -
    -
    -
    Feature Flags
    -
      - {flags.map(({ key, value }) => { - const actualValue = overrides.has(key) ? overrides.get(key) : value; - - return ( -
    • - {typeof actualValue === "boolean" ? ( - - ) : ( - <> - {key} - {actualValue?.toString()} - - )} -
    • - ); - })} -
    + + {children} +
    +
    + + + + + + + + + + + + + + + +
    +
    +
    Feature Flags
    +
      + {flags.map(({ key, value }) => { + const actualValue = overrides.has(key) ? overrides.get(key) : value; + + return ( +
    • + {typeof actualValue === "boolean" ? ( + + ) : ( + <> + {key} + {actualValue?.toString()} + + )} +
    • + ); + })} +
    +
    -
    + ); } diff --git a/src/index.tsx b/src/index.tsx index a246ec1..7d01388 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,30 +1,9 @@ import React, { PropsWithChildren } from "react"; import { prefab, ConfigValue, Context } from "@prefab-cloud/prefab-cloud-js"; -import version from "./version"; -import FlagToggleOverlay from "./FlagToggleOverlay"; -type ContextValue = number | string | boolean; -type ContextAttributes = { [key: string]: Record }; - -type ProvidedContext = { - get: (key: string) => any; - contextAttributes: ContextAttributes; - isEnabled: (key: string) => boolean; - loading: boolean; - prefab: typeof prefab; - keys: string[]; -}; - -const defaultContext: ProvidedContext = { - get: (_: string) => undefined, - isEnabled: (_: string) => false, - keys: [], - loading: true, - contextAttributes: {}, - prefab, -}; - -const PrefabContext = React.createContext(defaultContext); +import FlagToggleOverlay from "./FlagToggleOverlay"; +import { ContextAttributes, PrefabContext, ProvidedContext } from "./types"; +import version from "./version"; const usePrefab = () => React.useContext(PrefabContext); @@ -41,7 +20,6 @@ type Props = { afterEvaluationCallback?: EvaluationCallback; collectEvaluationSummaries?: boolean; collectLoggerNames?: boolean; - allowTogglingUi?: boolean; }; function PrefabProvider({ @@ -56,7 +34,6 @@ function PrefabProvider({ afterEvaluationCallback = undefined, collectEvaluationSummaries = false, collectLoggerNames = false, - allowTogglingUi = false, }: PropsWithChildren) { // We use this state to prevent a double-init when useEffect fires due to // StrictMode @@ -67,8 +44,6 @@ function PrefabProvider({ // Here we track the current identity so we can reload our config when it // changes const [loadedContextKey, setLoadedContextKey] = React.useState(""); - // Here we store overrides for the flag toggling UI - const [overrides, setOverrides] = React.useState>(new Map()); if (Object.keys(contextAttributes).length === 0) { // eslint-disable-next-line no-console @@ -77,8 +52,6 @@ function PrefabProvider({ ); } - const usageRef = React.useRef(new Map()); - const context = new Context(contextAttributes); const contextKey = context.encode(); @@ -136,60 +109,17 @@ function PrefabProvider({ const value: ProvidedContext = React.useMemo( () => ({ - isEnabled: (key: string) => { - if (overrides.has(key)) { - return overrides.get(key) as boolean; - } - const result = prefab.isEnabled.bind(prefab)(key); - usageRef.current.set(key, result); - return result; - }, + isEnabled: prefab.isEnabled.bind(prefab), contextAttributes, - get: (key: string) => { - if (overrides.has(key)) { - return overrides.get(key); - } - const result = prefab.get.bind(prefab)(key); - usageRef.current.set(key, result); - return result; - }, + get: prefab.get.bind(prefab), keys: Object.keys(prefab.configs), prefab, loading, }), - [loadedContextKey, loading, prefab, overrides] + [loadedContextKey, loading, prefab] ); - // const requestedFlags = () => prefab.requestedFlags.bind(prefab)(); - const requestedFlags = () => - Array.from(usageRef.current.entries()) - .map(([key, val]) => ({ - key, - value: val, - })) - .sort((a, b) => a.key.localeCompare(b.key)); - - return ( - - {children} - {/* TODO: not sure this actually has to block on loading any more */} - {allowTogglingUi && !loading && ( - { - console.log("addOverride: ", key, newValue); - const newOverrides = new Map(overrides); - newOverrides.set(key, newValue); - setOverrides(newOverrides); - }} - clearFlags={() => { - usageRef.current.clear(); - }} - /> - )} - - ); + return {children}; } type TestProps = { @@ -218,6 +148,7 @@ function PrefabTestProvider({ config, children }: PropsWithChildren) export { PrefabProvider, PrefabTestProvider, + FlagToggleOverlay, usePrefab, TestProps, Props, diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..3e2eb86 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,26 @@ +import { createContext } from "react"; +import { prefab } from "@prefab-cloud/prefab-cloud-js"; + +type ContextValue = number | string | boolean; + +export type ContextAttributes = { [key: string]: Record }; + +export type ProvidedContext = { + get: (key: string) => any; + contextAttributes: ContextAttributes; + isEnabled: (key: string) => boolean; + loading: boolean; + prefab: typeof prefab; + keys: string[]; +}; + +const defaultContext: ProvidedContext = { + get: (_: string) => undefined, + isEnabled: (_: string) => false, + keys: [], + loading: true, + contextAttributes: {}, + prefab, +}; + +export const PrefabContext = createContext(defaultContext); From 87c8433af583ca9f3cdd6e69243900dd1285ecfa Mon Sep 17 00:00:00 2001 From: ayip8 <16192761+ayip8@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:54:29 -0400 Subject: [PATCH 9/9] WIP only poll when overlay is showing --- src/FlagToggleOverlay.tsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/FlagToggleOverlay.tsx b/src/FlagToggleOverlay.tsx index e4aeea8..d2500b2 100644 --- a/src/FlagToggleOverlay.tsx +++ b/src/FlagToggleOverlay.tsx @@ -8,6 +8,8 @@ function FlagToggleOverlay({ children }: { children: React.ReactNode | undefined const [flags, setFlags] = useState<{ key: string; value: ConfigValue }[]>([]); const [overrides, setOverrides] = useState>(new Map()); + // we can't keep track of usage in a state variable, + // because evaluation calls from child component render methods would trigger an infinite render loop const usageRef = useRef(new Map()); const addOverride = (key: string, value: ConfigValue) => { @@ -49,15 +51,19 @@ function FlagToggleOverlay({ children }: { children: React.ReactNode | undefined [parentContext, overrides] ); - // poll for flags - // TODO turn this on and off based on whether the overlay is visible + // changes to the data in usageRef don't trigger a render, + // so we need to poll for changes useEffect(() => { - const interval = setInterval(() => { - setFlags(Array.from(usageRef.current.entries()).map(([key, value]) => ({ key, value }))); - }, 1000); + if (showOverlay) { + const interval = setInterval(() => { + setFlags(Array.from(usageRef.current.entries()).map(([key, value]) => ({ key, value }))); + }, 1000); - return () => clearInterval(interval); - }, []); + return () => clearInterval(interval); + } + + return () => {}; + }, [showOverlay]); // setup keyboard shortcut listener to toggle overlay useEffect(() => { @@ -201,7 +207,7 @@ function FlagToggleOverlay({ children }: { children: React.ReactNode | undefined addOverride(key, overrides.has(key) ? !overrides.get(key) : !value); }} /> -
    +
    {key}