From a8d1d49264d230d0464fef326e6a92c27977b0d4 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Thu, 5 Oct 2023 12:30:43 -0500 Subject: [PATCH 1/9] persist framework and mode to future page loads --- src/components/global/Playground/index.tsx | 63 ++++++++++++++++++---- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 1e61832fa33..8be6a714e5d 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -48,13 +48,13 @@ const ControlButton = ({ return controlButton; }; -const CodeBlockButton = ({ language, usageTarget, setUsageTarget, disabled }) => { +const CodeBlockButton = ({ language, usageTarget, setAndSaveUsageTarget, disabled }) => { const langValue = UsageTarget[language]; return ( { - setUsageTarget(langValue); + setAndSaveUsageTarget(langValue); }} title={`Show ${language} code`} label={language} @@ -164,15 +164,47 @@ export default function Playground({ const frameMD = useRef(null); const consoleBodyRef = useRef(null); - const defaultMode = typeof mode !== 'undefined' ? mode : Mode.iOS; + const getDefaultMode = () => { + /** + * If a custom mode was specified, use that. + */ + if (mode) return mode; + + /** + * Otherwise, if there is a saved mode from previously clicking + * the mode button, use that. + */ + const storedMode = localStorage.getItem(MODE_STORAGE_KEY); + if (storedMode) return storedMode; + + /** + * Default to iOS mode as a fallback. + */ + return Mode.iOS; + }; const getDefaultUsageTarget = () => { - // If defined, Angular target should be the default + /** + * If there is a saved target from previously clicking the + * framework buttons, and there is code for it, use that. + */ + const storedTarget = localStorage.getItem(USAGE_TARGET_STORAGE_KEY); + if (storedTarget && code[storedTarget] !== undefined) { + return storedTarget; + } + + /** + * If there is no saved target, and Angular code is available, + * default to that. + */ if (code[UsageTarget.Angular] !== undefined) { return UsageTarget.Angular; } - // Otherwise, default to the first target passed. + /** + * If there is no Angular code available, fall back to the + * first available framework. + */ return Object.keys(code)[0]; }; @@ -182,7 +214,7 @@ export default function Playground({ */ const frameSize = FRAME_SIZES[size] || size; const [usageTarget, setUsageTarget] = useState(getDefaultUsageTarget()); - const [ionicMode, setIonicMode] = useState(defaultMode); + const [ionicMode, setIonicMode] = useState(getDefaultMode()); const [codeSnippets, setCodeSnippets] = useState({}); const [renderIframes, setRenderIframes] = useState(false); const [iframesLoaded, setIframesLoaded] = useState(false); @@ -196,6 +228,16 @@ export default function Playground({ */ const [resetCount, setResetCount] = useState(0); + const setAndSaveMode = (mode: Mode) => { + localStorage.setItem(MODE_STORAGE_KEY, mode); + setIonicMode(mode); + }; + + const setAndSaveUsageTarget = (target: UsageTarget) => { + localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); + setUsageTarget(target); + }; + /** * Rather than encode isDarkTheme into the frame source * url, we post a message to each frame so that @@ -526,7 +568,7 @@ export default function Playground({ key={`code-block-${lang}`} language={lang} usageTarget={usageTarget} - setUsageTarget={setUsageTarget} + setAndSaveUsageTarget={setAndSaveUsageTarget} disabled={!hasCode} /> ); @@ -536,14 +578,14 @@ export default function Playground({ setIonicMode(Mode.iOS)} + handleClick={() => setAndSaveMode(Mode.iOS)} title="iOS mode" label="iOS" /> setIonicMode(Mode.MD)} + handleClick={() => setAndSaveMode(Mode.MD)} title="MD mode" label="MD" /> @@ -750,3 +792,6 @@ const isFrameReady = (frame: HTMLIFrameElement) => { } return (frame.contentWindow as any).demoReady === true; }; + +const USAGE_TARGET_STORAGE_KEY = 'playground_usage_target'; +const MODE_STORAGE_KEY = 'playground_mode'; \ No newline at end of file From c359a6feb74762474fd2a2413cf574cbbc93986f Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Thu, 5 Oct 2023 14:12:12 -0500 Subject: [PATCH 2/9] update all other playgrounds on page in sync --- src/components/global/Playground/index.tsx | 57 +++++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 8be6a714e5d..1384ca894fa 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { RefObject, forwardRef, useEffect, useMemo, useRef, useState } from 'react'; import useBaseUrl from '@docusaurus/useBaseUrl'; import './playground.css'; @@ -13,7 +13,9 @@ import TabItem from '@theme/TabItem'; import { IconHtml, IconTs, IconVue, IconDefault, IconCss, IconDots } from './icons'; -const ControlButton = ({ +import { useScrollPositionBlocker } from '@docusaurus/theme-common'; + +const ControlButton = forwardRef(({ isSelected, handleClick, title, @@ -25,7 +27,7 @@ const ControlButton = ({ title: string; label: string; disabled?: boolean; -}) => { +}, ref: RefObject) => { const controlButton = ( @@ -46,19 +49,22 @@ const ControlButton = ({ ); } return controlButton; -}; +}); const CodeBlockButton = ({ language, usageTarget, setAndSaveUsageTarget, disabled }) => { + const buttonRef = useRef(null); const langValue = UsageTarget[language]; + return ( { - setAndSaveUsageTarget(langValue); + setAndSaveUsageTarget(langValue, buttonRef.current); }} title={`Show ${language} code`} label={language} disabled={disabled} + ref={buttonRef} /> ); }; @@ -164,6 +170,8 @@ export default function Playground({ const frameMD = useRef(null); const consoleBodyRef = useRef(null); + const { blockElementScrollPositionUntilNextRender } = useScrollPositionBlocker(); + const getDefaultMode = () => { /** * If a custom mode was specified, use that. @@ -231,11 +239,28 @@ export default function Playground({ const setAndSaveMode = (mode: Mode) => { localStorage.setItem(MODE_STORAGE_KEY, mode); setIonicMode(mode); + window.dispatchEvent(new CustomEvent(MODE_UPDATED_EVENT, { + detail: mode + })); }; - const setAndSaveUsageTarget = (target: UsageTarget) => { + const setAndSaveUsageTarget = (target: UsageTarget, tab: HTMLElement) => { localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); setUsageTarget(target); + + /** + * This prevents the scroll position from jumping around if + * there is a playground above this one with code that changes + * in length between frameworks. + * + * Note that we don't need this when changing the mode because + * the two mode iframes are always the same height. + */ + blockElementScrollPositionUntilNextRender(tab); + + window.dispatchEvent(new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { + detail: target + })); }; /** @@ -366,6 +391,22 @@ export default function Playground({ io.observe(hostRef.current!); }); + useEffect(() => { + window.addEventListener(MODE_UPDATED_EVENT, (e: CustomEvent) => { + const mode = e.detail; + if (Object.values(Mode).includes(mode)) { + setIonicMode(mode); // don't use setAndSave to avoid infinite loop + } + }); + + window.addEventListener(USAGE_TARGET_UPDATED_EVENT, (e: CustomEvent) => { + const usageTarget = e.detail; + if (Object.values(UsageTarget).includes(usageTarget)) { + setUsageTarget(usageTarget); // don't use setAndSave to avoid infinite loop + } + }); + }, []); + const isIOS = ionicMode === Mode.iOS; const isMD = ionicMode === Mode.MD; @@ -794,4 +835,6 @@ const isFrameReady = (frame: HTMLIFrameElement) => { }; const USAGE_TARGET_STORAGE_KEY = 'playground_usage_target'; -const MODE_STORAGE_KEY = 'playground_mode'; \ No newline at end of file +const MODE_STORAGE_KEY = 'playground_mode'; +const USAGE_TARGET_UPDATED_EVENT = 'playground-usage-target-updated'; +const MODE_UPDATED_EVENT = 'playground-event-updated'; \ No newline at end of file From 1f2ae19dbb868d057259997425114abf70b0ad33 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Thu, 5 Oct 2023 14:16:30 -0500 Subject: [PATCH 3/9] lint --- src/components/global/Playground/index.tsx | 91 ++++++++++++---------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 1384ca894fa..563cdffc6fa 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -15,41 +15,46 @@ import { IconHtml, IconTs, IconVue, IconDefault, IconCss, IconDots } from './ico import { useScrollPositionBlocker } from '@docusaurus/theme-common'; -const ControlButton = forwardRef(({ - isSelected, - handleClick, - title, - label, - disabled, -}: { - isSelected: boolean; - handleClick: () => void; - title: string; - label: string; - disabled?: boolean; -}, ref: RefObject) => { - const controlButton = ( - - ); - if (disabled) { - return ( - - {/* Tippy requires a wrapper element for disabled elements: https://atomiks.github.io/tippyjs/v5/creating-tooltips/#disabled-elements */} -
{controlButton}
-
+const ControlButton = forwardRef( + ( + { + isSelected, + handleClick, + title, + label, + disabled, + }: { + isSelected: boolean; + handleClick: () => void; + title: string; + label: string; + disabled?: boolean; + }, + ref: RefObject + ) => { + const controlButton = ( + ); + if (disabled) { + return ( + + {/* Tippy requires a wrapper element for disabled elements: https://atomiks.github.io/tippyjs/v5/creating-tooltips/#disabled-elements */} +
{controlButton}
+
+ ); + } + return controlButton; } - return controlButton; -}); +); const CodeBlockButton = ({ language, usageTarget, setAndSaveUsageTarget, disabled }) => { const buttonRef = useRef(null); @@ -239,9 +244,11 @@ export default function Playground({ const setAndSaveMode = (mode: Mode) => { localStorage.setItem(MODE_STORAGE_KEY, mode); setIonicMode(mode); - window.dispatchEvent(new CustomEvent(MODE_UPDATED_EVENT, { - detail: mode - })); + window.dispatchEvent( + new CustomEvent(MODE_UPDATED_EVENT, { + detail: mode, + }) + ); }; const setAndSaveUsageTarget = (target: UsageTarget, tab: HTMLElement) => { @@ -252,15 +259,17 @@ export default function Playground({ * This prevents the scroll position from jumping around if * there is a playground above this one with code that changes * in length between frameworks. - * + * * Note that we don't need this when changing the mode because * the two mode iframes are always the same height. */ blockElementScrollPositionUntilNextRender(tab); - window.dispatchEvent(new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { - detail: target - })); + window.dispatchEvent( + new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { + detail: target, + }) + ); }; /** @@ -837,4 +846,4 @@ const isFrameReady = (frame: HTMLIFrameElement) => { const USAGE_TARGET_STORAGE_KEY = 'playground_usage_target'; const MODE_STORAGE_KEY = 'playground_mode'; const USAGE_TARGET_UPDATED_EVENT = 'playground-usage-target-updated'; -const MODE_UPDATED_EVENT = 'playground-event-updated'; \ No newline at end of file +const MODE_UPDATED_EVENT = 'playground-event-updated'; From 1339c2c53aa54585faaa1f33a693a9e758bcb355 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Thu, 5 Oct 2023 14:33:23 -0500 Subject: [PATCH 4/9] add more comments --- src/components/global/Playground/index.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 563cdffc6fa..4b3da421a7e 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -244,6 +244,11 @@ export default function Playground({ const setAndSaveMode = (mode: Mode) => { localStorage.setItem(MODE_STORAGE_KEY, mode); setIonicMode(mode); + + /** + * Tell other playgrounds on the page that the mode has + * updated, so they can sync up. + */ window.dispatchEvent( new CustomEvent(MODE_UPDATED_EVENT, { detail: mode, @@ -265,6 +270,10 @@ export default function Playground({ */ blockElementScrollPositionUntilNextRender(tab); + /** + * Tell other playgrounds on the page that the framework + * has updated, so they can sync up. + */ window.dispatchEvent( new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { detail: target, @@ -400,6 +409,10 @@ export default function Playground({ io.observe(hostRef.current!); }); + /** + * Listen for any playground on the page to have its mode or framework + * updated so this playground can switch to the same setting. + */ useEffect(() => { window.addEventListener(MODE_UPDATED_EVENT, (e: CustomEvent) => { const mode = e.detail; From c88edbf880bde0397fcd24bd942dd41335e9a8de Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Thu, 5 Oct 2023 14:42:33 -0500 Subject: [PATCH 5/9] check if window is defined before accessing localStorage --- src/components/global/Playground/index.tsx | 68 ++++++++++++---------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 4b3da421a7e..19478c4affc 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -187,7 +187,7 @@ export default function Playground({ * Otherwise, if there is a saved mode from previously clicking * the mode button, use that. */ - const storedMode = localStorage.getItem(MODE_STORAGE_KEY); + const storedMode = window !== undefined ? localStorage.getItem(MODE_STORAGE_KEY) : null; if (storedMode) return storedMode; /** @@ -201,7 +201,7 @@ export default function Playground({ * If there is a saved target from previously clicking the * framework buttons, and there is code for it, use that. */ - const storedTarget = localStorage.getItem(USAGE_TARGET_STORAGE_KEY); + const storedTarget = window !== undefined ? localStorage.getItem(USAGE_TARGET_STORAGE_KEY) : null; if (storedTarget && code[storedTarget] !== undefined) { return storedTarget; } @@ -242,43 +242,49 @@ export default function Playground({ const [resetCount, setResetCount] = useState(0); const setAndSaveMode = (mode: Mode) => { - localStorage.setItem(MODE_STORAGE_KEY, mode); setIonicMode(mode); - /** - * Tell other playgrounds on the page that the mode has - * updated, so they can sync up. - */ - window.dispatchEvent( - new CustomEvent(MODE_UPDATED_EVENT, { - detail: mode, - }) - ); + if (window !== undefined) { + localStorage.setItem(MODE_STORAGE_KEY, mode); + + /** + * Tell other playgrounds on the page that the mode has + * updated, so they can sync up. + */ + window.dispatchEvent( + new CustomEvent(MODE_UPDATED_EVENT, { + detail: mode, + }) + ); + } }; const setAndSaveUsageTarget = (target: UsageTarget, tab: HTMLElement) => { - localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); setUsageTarget(target); - /** - * This prevents the scroll position from jumping around if - * there is a playground above this one with code that changes - * in length between frameworks. - * - * Note that we don't need this when changing the mode because - * the two mode iframes are always the same height. - */ - blockElementScrollPositionUntilNextRender(tab); + if (window !== undefined) { + localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); - /** - * Tell other playgrounds on the page that the framework - * has updated, so they can sync up. - */ - window.dispatchEvent( - new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { - detail: target, - }) - ); + /** + * This prevents the scroll position from jumping around if + * there is a playground above this one with code that changes + * in length between frameworks. + * + * Note that we don't need this when changing the mode because + * the two mode iframes are always the same height. + */ + blockElementScrollPositionUntilNextRender(tab); + + /** + * Tell other playgrounds on the page that the framework + * has updated, so they can sync up. + */ + window.dispatchEvent( + new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { + detail: target, + }) + ); + } }; /** From 4664ee04671175aa64223305cc763182df2865e3 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Fri, 6 Oct 2023 09:12:29 -0500 Subject: [PATCH 6/9] Revert "check if window is defined before accessing localStorage" This reverts commit c88edbf880bde0397fcd24bd942dd41335e9a8de. --- src/components/global/Playground/index.tsx | 68 ++++++++++------------ 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 19478c4affc..4b3da421a7e 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -187,7 +187,7 @@ export default function Playground({ * Otherwise, if there is a saved mode from previously clicking * the mode button, use that. */ - const storedMode = window !== undefined ? localStorage.getItem(MODE_STORAGE_KEY) : null; + const storedMode = localStorage.getItem(MODE_STORAGE_KEY); if (storedMode) return storedMode; /** @@ -201,7 +201,7 @@ export default function Playground({ * If there is a saved target from previously clicking the * framework buttons, and there is code for it, use that. */ - const storedTarget = window !== undefined ? localStorage.getItem(USAGE_TARGET_STORAGE_KEY) : null; + const storedTarget = localStorage.getItem(USAGE_TARGET_STORAGE_KEY); if (storedTarget && code[storedTarget] !== undefined) { return storedTarget; } @@ -242,49 +242,43 @@ export default function Playground({ const [resetCount, setResetCount] = useState(0); const setAndSaveMode = (mode: Mode) => { + localStorage.setItem(MODE_STORAGE_KEY, mode); setIonicMode(mode); - if (window !== undefined) { - localStorage.setItem(MODE_STORAGE_KEY, mode); - - /** - * Tell other playgrounds on the page that the mode has - * updated, so they can sync up. - */ - window.dispatchEvent( - new CustomEvent(MODE_UPDATED_EVENT, { - detail: mode, - }) - ); - } + /** + * Tell other playgrounds on the page that the mode has + * updated, so they can sync up. + */ + window.dispatchEvent( + new CustomEvent(MODE_UPDATED_EVENT, { + detail: mode, + }) + ); }; const setAndSaveUsageTarget = (target: UsageTarget, tab: HTMLElement) => { + localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); setUsageTarget(target); - if (window !== undefined) { - localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); - - /** - * This prevents the scroll position from jumping around if - * there is a playground above this one with code that changes - * in length between frameworks. - * - * Note that we don't need this when changing the mode because - * the two mode iframes are always the same height. - */ - blockElementScrollPositionUntilNextRender(tab); + /** + * This prevents the scroll position from jumping around if + * there is a playground above this one with code that changes + * in length between frameworks. + * + * Note that we don't need this when changing the mode because + * the two mode iframes are always the same height. + */ + blockElementScrollPositionUntilNextRender(tab); - /** - * Tell other playgrounds on the page that the framework - * has updated, so they can sync up. - */ - window.dispatchEvent( - new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { - detail: target, - }) - ); - } + /** + * Tell other playgrounds on the page that the framework + * has updated, so they can sync up. + */ + window.dispatchEvent( + new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { + detail: target, + }) + ); }; /** From 4010895a46dc91bc147d2f4993f633d5a90ea3c3 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Fri, 6 Oct 2023 09:47:28 -0500 Subject: [PATCH 7/9] add useIsBrowser to handle app being built in SSR --- src/components/global/Playground/index.tsx | 123 +++++++++++++-------- 1 file changed, 79 insertions(+), 44 deletions(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 4b3da421a7e..552fb268584 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -14,6 +14,7 @@ import TabItem from '@theme/TabItem'; import { IconHtml, IconTs, IconVue, IconDefault, IconCss, IconDots } from './icons'; import { useScrollPositionBlocker } from '@docusaurus/theme-common'; +import useIsBrowser from '@docusaurus/useIsBrowser'; const ControlButton = forwardRef( ( @@ -169,6 +170,13 @@ export default function Playground({ const { isDarkTheme } = useThemeContext(); + /** + * When deploying, Docusaurus builds the app in an SSR environment. + * We need to check whether we're in a browser so we know if we can + * use the window or localStorage objects. + */ + const isBrowser = useIsBrowser(); + const hostRef = useRef(null); const codeRef = useRef(null); const frameiOS = useRef(null); @@ -242,43 +250,49 @@ export default function Playground({ const [resetCount, setResetCount] = useState(0); const setAndSaveMode = (mode: Mode) => { - localStorage.setItem(MODE_STORAGE_KEY, mode); setIonicMode(mode); - /** - * Tell other playgrounds on the page that the mode has - * updated, so they can sync up. - */ - window.dispatchEvent( - new CustomEvent(MODE_UPDATED_EVENT, { - detail: mode, - }) - ); + if (isBrowser) { + localStorage.setItem(MODE_STORAGE_KEY, mode); + + /** + * Tell other playgrounds on the page that the mode has + * updated, so they can sync up. + */ + window.dispatchEvent( + new CustomEvent(MODE_UPDATED_EVENT, { + detail: mode, + }) + ); + } }; const setAndSaveUsageTarget = (target: UsageTarget, tab: HTMLElement) => { - localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); setUsageTarget(target); - /** - * This prevents the scroll position from jumping around if - * there is a playground above this one with code that changes - * in length between frameworks. - * - * Note that we don't need this when changing the mode because - * the two mode iframes are always the same height. - */ - blockElementScrollPositionUntilNextRender(tab); + if (isBrowser) { + localStorage.setItem(USAGE_TARGET_STORAGE_KEY, target); - /** - * Tell other playgrounds on the page that the framework - * has updated, so they can sync up. - */ - window.dispatchEvent( - new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { - detail: target, - }) - ); + /** + * This prevents the scroll position from jumping around if + * there is a playground above this one with code that changes + * in length between frameworks. + * + * Note that we don't need this when changing the mode because + * the two mode iframes are always the same height. + */ + blockElementScrollPositionUntilNextRender(tab); + + /** + * Tell other playgrounds on the page that the framework + * has updated, so they can sync up. + */ + window.dispatchEvent( + new CustomEvent(USAGE_TARGET_UPDATED_EVENT, { + detail: target, + }) + ); + } }; /** @@ -410,24 +424,45 @@ export default function Playground({ }); /** - * Listen for any playground on the page to have its mode or framework - * updated so this playground can switch to the same setting. + * Sometimes, the app isn't fully hydrated on the first render, + * causing isBrowser to be set to false even if running the app + * in a browser (vs. SSR). isBrowser is then updated on the next + * render cycle. + * + * This useEffect contains code that can only run in the browser, + * and also needs to run on that first go-around. Note that + * isBrowser will never be set from true back to false, so the + * code within the if(isBrowser) check will only run once. */ useEffect(() => { - window.addEventListener(MODE_UPDATED_EVENT, (e: CustomEvent) => { - const mode = e.detail; - if (Object.values(Mode).includes(mode)) { - setIonicMode(mode); // don't use setAndSave to avoid infinite loop - } - }); + if (isBrowser) { + /** + * Load the stored mode and/or usage target, if present + * from previously being toggled. + */ + const storedMode = localStorage.getItem(MODE_STORAGE_KEY); + if (storedMode) setIonicMode(storedMode); + const storedUsageTarget = localStorage.getItem(USAGE_TARGET_STORAGE_KEY); + if (storedUsageTarget) setUsageTarget(storedUsageTarget); - window.addEventListener(USAGE_TARGET_UPDATED_EVENT, (e: CustomEvent) => { - const usageTarget = e.detail; - if (Object.values(UsageTarget).includes(usageTarget)) { - setUsageTarget(usageTarget); // don't use setAndSave to avoid infinite loop - } - }); - }, []); + /** + * Listen for any playground on the page to have its mode or framework + * updated so this playground can switch to the same setting. + */ + window.addEventListener(MODE_UPDATED_EVENT, (e: CustomEvent) => { + const mode = e.detail; + if (Object.values(Mode).includes(mode)) { + setIonicMode(mode); // don't use setAndSave to avoid infinite loop + } + }); + window.addEventListener(USAGE_TARGET_UPDATED_EVENT, (e: CustomEvent) => { + const usageTarget = e.detail; + if (Object.values(UsageTarget).includes(usageTarget)) { + setUsageTarget(usageTarget); // don't use setAndSave to avoid infinite loop + } + }); + } + }, [isBrowser]); const isIOS = ionicMode === Mode.iOS; const isMD = ionicMode === Mode.MD; From 33badb18801a920242afa401ea9d854dc5f20216 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Fri, 6 Oct 2023 09:49:47 -0500 Subject: [PATCH 8/9] check isBrowser before accessing localStorage in getDefault utils --- src/components/global/Playground/index.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 552fb268584..8e12ed9eed2 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -195,8 +195,10 @@ export default function Playground({ * Otherwise, if there is a saved mode from previously clicking * the mode button, use that. */ - const storedMode = localStorage.getItem(MODE_STORAGE_KEY); - if (storedMode) return storedMode; + if (isBrowser) { + const storedMode = localStorage.getItem(MODE_STORAGE_KEY); + if (storedMode) return storedMode; + } /** * Default to iOS mode as a fallback. @@ -209,9 +211,11 @@ export default function Playground({ * If there is a saved target from previously clicking the * framework buttons, and there is code for it, use that. */ - const storedTarget = localStorage.getItem(USAGE_TARGET_STORAGE_KEY); - if (storedTarget && code[storedTarget] !== undefined) { - return storedTarget; + if (isBrowser) { + const storedTarget = localStorage.getItem(USAGE_TARGET_STORAGE_KEY); + if (storedTarget && code[storedTarget] !== undefined) { + return storedTarget; + } } /** From 0507ac4f9d4d05e0440086096971811c14ef5749 Mon Sep 17 00:00:00 2001 From: amandaesmith3 Date: Fri, 6 Oct 2023 09:50:21 -0500 Subject: [PATCH 9/9] lint --- src/components/global/Playground/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx index 8e12ed9eed2..2f46fe23049 100644 --- a/src/components/global/Playground/index.tsx +++ b/src/components/global/Playground/index.tsx @@ -432,7 +432,7 @@ export default function Playground({ * causing isBrowser to be set to false even if running the app * in a browser (vs. SSR). isBrowser is then updated on the next * render cycle. - * + * * This useEffect contains code that can only run in the browser, * and also needs to run on that first go-around. Note that * isBrowser will never be set from true back to false, so the