From 2b151e958ead92ae1575088d1e0c009cd1262854 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 5 Jun 2026 13:50:51 -0500 Subject: [PATCH] Gate React Query devtools behind dev toggle --- packages/web/src/app/AppProviders.tsx | 7 ++- packages/web/src/hooks/useDevToggle.ts | 58 +++++++++++++++++++ packages/web/src/pages/dev-tools/DevTools.tsx | 18 ++++++ packages/web/src/pages/dev-tools/messages.ts | 7 ++- 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/web/src/hooks/useDevToggle.ts diff --git a/packages/web/src/app/AppProviders.tsx b/packages/web/src/app/AppProviders.tsx index 3fdfe68e58d..10cd522e412 100644 --- a/packages/web/src/app/AppProviders.tsx +++ b/packages/web/src/app/AppProviders.tsx @@ -15,6 +15,7 @@ import { import { PersistGate } from 'redux-persist/integration/react' import { WagmiProvider } from 'wagmi' +import { REACT_QUERY_DEVTOOLS_KEY, useDevToggle } from 'hooks/useDevToggle' import { useIsMobile } from 'hooks/useIsMobile' import { env } from 'services/env' import { queryClient } from 'services/query-client' @@ -41,6 +42,10 @@ type AppProvidersProps = { export const AppProviders = ({ children }: AppProvidersProps) => { const isMobile = useIsMobile() + const [reactQueryDevtoolsEnabled] = useDevToggle( + REACT_QUERY_DEVTOOLS_KEY, + false + ) const [{ store, persistor }] = useState(() => { const theme = getTheme() @@ -95,7 +100,7 @@ export const AppProviders = ({ children }: AppProvidersProps) => { - + {reactQueryDevtoolsEnabled ? : null} ) diff --git a/packages/web/src/hooks/useDevToggle.ts b/packages/web/src/hooks/useDevToggle.ts new file mode 100644 index 00000000000..8f8efceec7d --- /dev/null +++ b/packages/web/src/hooks/useDevToggle.ts @@ -0,0 +1,58 @@ +import { useCallback, useEffect, useState } from 'react' + +const CHANGE_EVENT = 'audius:dev-toggle-change' + +const read = (key: string, defaultValue: boolean): boolean => { + if (typeof window === 'undefined') return defaultValue + + try { + const raw = window.localStorage.getItem(key) + if (raw === null) return defaultValue + return raw === 'true' + } catch { + return defaultValue + } +} + +export const useDevToggle = ( + key: string, + defaultValue: boolean +): [boolean, (next: boolean) => void] => { + const [value, setValue] = useState(() => read(key, defaultValue)) + + useEffect(() => { + const sync = (event: Event) => { + if (event instanceof CustomEvent && event.detail?.key === key) { + setValue(read(key, defaultValue)) + } + + if (event instanceof StorageEvent && event.key === key) { + setValue(read(key, defaultValue)) + } + } + + window.addEventListener(CHANGE_EVENT, sync) + window.addEventListener('storage', sync) + + return () => { + window.removeEventListener(CHANGE_EVENT, sync) + window.removeEventListener('storage', sync) + } + }, [key, defaultValue]) + + const set = useCallback( + (next: boolean) => { + try { + window.localStorage.setItem(key, String(next)) + } catch {} + + setValue(next) + window.dispatchEvent(new CustomEvent(CHANGE_EVENT, { detail: { key } })) + }, + [key] + ) + + return [value, set] +} + +export const REACT_QUERY_DEVTOOLS_KEY = 'audius-react-query-devtools-enabled' diff --git a/packages/web/src/pages/dev-tools/DevTools.tsx b/packages/web/src/pages/dev-tools/DevTools.tsx index 2d835a6e28b..0dcc606013a 100644 --- a/packages/web/src/pages/dev-tools/DevTools.tsx +++ b/packages/web/src/pages/dev-tools/DevTools.tsx @@ -9,6 +9,7 @@ import { IconShieldCheck, IconDashboard, IconFanClub, + IconRefresh, IconUser, Paper, Text, @@ -19,6 +20,7 @@ import { useNavigate } from 'react-router' import { Header } from 'components/header/desktop/Header' import { Page } from 'components/page/Page' +import { REACT_QUERY_DEVTOOLS_KEY, useDevToggle } from 'hooks/useDevToggle' import { env } from 'services/env' import { messages } from './messages' @@ -102,6 +104,8 @@ export const DevTools = () => { const dispatch = useDispatch() const navigate = useNavigate() const { onOpen: openCoinSuccessModal } = useCoinSuccessModal() + const [reactQueryDevtoolsEnabled, setReactQueryDevtoolsEnabled] = + useDevToggle(REACT_QUERY_DEVTOOLS_KEY, false) const handleOpenFeatureFlags = () => { dispatch( @@ -217,6 +221,20 @@ export const DevTools = () => { buttonText={messages.coinSuccessModalPreviewButton} onButtonClick={handleOpenCoinSuccessModalPreview} /> + + + setReactQueryDevtoolsEnabled(!reactQueryDevtoolsEnabled) + } + /> diff --git a/packages/web/src/pages/dev-tools/messages.ts b/packages/web/src/pages/dev-tools/messages.ts index 613e61e1841..45524afa157 100644 --- a/packages/web/src/pages/dev-tools/messages.ts +++ b/packages/web/src/pages/dev-tools/messages.ts @@ -46,5 +46,10 @@ export const messages = { coinSuccessModalPreviewTitle: 'Coin success modal', coinSuccessModalPreviewDescription: 'Open the post-launch fan club success dialog with sample coin data (UI preview only).', - coinSuccessModalPreviewButton: 'Open coin success modal' + coinSuccessModalPreviewButton: 'Open coin success modal', + reactQueryDevtoolsTitle: 'React Query Devtools', + reactQueryDevtoolsDescription: + 'Show the floating React Query devtools button for inspecting queries, mutations, and cache state. Off by default.', + reactQueryDevtoolsEnable: 'Show Devtools Button', + reactQueryDevtoolsDisable: 'Hide Devtools Button' }