@samitouri / QOS-React / commits / 250f1b20e0

[playground] Fix useEffect on tabify (#34594)

There was a bug in the Compiler Playground related to the "Show Internals" toggle due to a useEffect that was causing the tab names to flicker from a rerender. Rewritten instead with a `<Suspense>` boundary + `use`.

Eugene Choi committed Sep 25, 2025 at 14:56 UTC 250f1b20e0ac8e6c1ba03f2466ad63ff9b5104de
3 files changed +40 -11
compiler/apps/playground/components/Editor/Output.tsx
+34 -11
@@ -19,12 +19,13 @@ import {
19 import parserBabel from 'prettier/plugins/babel';
20 import * as prettierPluginEstree from 'prettier/plugins/estree';
21 import * as prettier from 'prettier/standalone';
22 -import {memo, ReactNode, useEffect, useState} from 'react';
22 import {type Store} from '../../lib/stores';
23 +import {memo, ReactNode, use, useState, Suspense} from 'react';
24 import AccordionWindow from '../AccordionWindow';
25 import TabbedWindow from '../TabbedWindow';
26 import {monacoOptions} from './monacoOptions';
27 import {BabelFileResult} from '@babel/core';
28 +import {LRUCache} from 'lru-cache';
29
30 const MemoizedOutput = memo(Output);
31
@@ -32,6 +33,10 @@ export default MemoizedOutput;
33
34 export const BASIC_OUTPUT_TAB_NAMES = ['Output', 'SourceMap'];
35
36 +const tabifyCache = new LRUCache<Store, Promise<Map<string, ReactNode>>>({
37 + max: 5,
38 +});
39 +
40 export type PrintedCompilerPipelineValue =
41 | {
42 kind: 'hir';
@@ -200,6 +205,25 @@ ${code}
205 return reorderedTabs;
206 }
207
208 +function tabifyCached(
209 + store: Store,
210 + compilerOutput: CompilerOutput,
211 +): Promise<Map<string, ReactNode>> {
212 + const cached = tabifyCache.get(store);
213 + if (cached) return cached;
214 + const result = tabify(store.source, compilerOutput, store.showInternals);
215 + tabifyCache.set(store, result);
216 + return result;
217 +}
218 +
219 +function Fallback(): JSX.Element {
220 + return (
221 + <div className="w-full h-monaco_small sm:h-monaco flex items-center justify-center">
222 + Loading...
223 + </div>
224 + );
225 +}
226 +
227 function utf16ToUTF8(s: string): string {
228 return unescape(encodeURIComponent(s));
229 }
@@ -213,12 +237,17 @@ function getSourceMapUrl(code: string, map: string): string | null {
237 }
238
239 function Output({store, compilerOutput}: Props): JSX.Element {
240 + return (
241 + <Suspense fallback={<Fallback />}>
242 + <OutputContent store={store} compilerOutput={compilerOutput} />
243 + </Suspense>
244 + );
245 +}
246 +
247 +function OutputContent({store, compilerOutput}: Props): JSX.Element {
248 const [tabsOpen, setTabsOpen] = useState<Set<string>>(
249 () => new Set(['Output']),
250 );
219 - const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
220 - () => new Map(),
221 - );
251 const [activeTab, setActiveTab] = useState<string>('Output');
252
253 /*
@@ -233,13 +262,6 @@ function Output({store, compilerOutput}: Props): JSX.Element {
262 setTabsOpen(new Set(['Output']));
263 setActiveTab('Output');
264 }
236 -
237 - useEffect(() => {
238 - tabify(store.source, compilerOutput, store.showInternals).then(tabs => {
239 - setTabs(tabs);
240 - });
241 - }, [store.source, compilerOutput, store.showInternals]);
242 -
265 const changedPasses: Set<string> = new Set(['Output', 'HIR']); // Initial and final passes should always be bold
266 let lastResult: string = '';
267 for (const [passName, results] of compilerOutput.results) {
@@ -254,6 +276,7 @@ function Output({store, compilerOutput}: Props): JSX.Element {
276 lastResult = currResult;
277 }
278 }
279 + const tabs = use(tabifyCached(store, compilerOutput));
280
281 if (!store.showInternals) {
282 return (
compiler/apps/playground/package.json
+1
@@ -32,6 +32,7 @@
32 "hermes-eslint": "^0.25.0",
33 "hermes-parser": "^0.25.0",
34 "invariant": "^2.2.4",
35 + "lru-cache": "^11.2.2",
36 "lz-string": "^1.5.0",
37 "monaco-editor": "^0.52.0",
38 "next": "15.6.0-canary.7",
compiler/apps/playground/yarn.lock
+5
@@ -3104,6 +3104,11 @@ lru-cache@^10.2.0:
3104 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
3105 integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
3106
3107 +lru-cache@^11.2.2:
3108 + version "11.2.2"
3109 + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.2.tgz#40fd37edffcfae4b2940379c0722dc6eeaa75f24"
3110 + integrity sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==
3111 +
3112 lru-cache@^5.1.1:
3113 version "5.1.1"
3114 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"