Skip to content

Commit be04948

Browse files
committed
chore(playground): add dynamic import to improve performace
1 parent 496cf62 commit be04948

4 files changed

Lines changed: 60 additions & 48 deletions

File tree

src/components/CodeEditor.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React, { useRef, useEffect, useState } from 'react'
44
import { useTheme } from '@/lib/theme/ThemeProvider'
55
import { Editor } from '@monaco-editor/react'
6-
import * as monaco from 'monaco-editor'
6+
import type * as monaco from 'monaco-editor'
77

88
interface CodeEditorProps {
99
code: string
@@ -23,35 +23,37 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
2323
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
2424
const { isDarkTheme, getColor } = useTheme()
2525
const [isEditorReady, setIsEditorReady] = useState(false)
26+
const [isMounted, setIsMounted] = useState(false)
2627

2728
const handleEditorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
2829
editorRef.current = editor
2930
setIsEditorReady(true)
3031
}
3132

32-
33+
useEffect(() => {
34+
setIsMounted(true)
35+
return () => setIsMounted(false)
36+
}, [])
37+
3338
useEffect(() => {
3439
if (editorRef.current) {
3540
editorRef.current.updateOptions({ readOnly: disabled })
3641
}
3742
}, [disabled])
3843

39-
4044
useEffect(() => {
41-
if (editorRef.current && code !== editorRef.current.getValue()) {
45+
if (editorRef.current && code !== editorRef.current.getValue() && isEditorReady) {
4246
editorRef.current.setValue(code)
4347
}
4448
}, [code, isEditorReady])
4549

46-
4750
const editorContainerStyle = {
4851
border: `1px solid ${getColor('border')}`,
4952
borderRadius: '0.375rem',
5053
overflow: 'hidden',
5154
height: height
5255
}
5356

54-
5557
const LoadingPlaceholder = () => (
5658
<div
5759
style={{
@@ -73,6 +75,10 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
7375
</div>
7476
)
7577

78+
if (!isMounted) {
79+
return <div style={editorContainerStyle}><LoadingPlaceholder /></div>
80+
}
81+
7682
return (
7783
<div style={editorContainerStyle}>
7884
<Editor
@@ -94,7 +100,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
94100
cursorBlinking: 'smooth',
95101
automaticLayout: true,
96102
lineNumbers: 'on',
97-
renderLineHighlight: 'all',
103+
renderLineHighlight: 'all',
98104
folding: true,
99105
contextmenu: true,
100106
formatOnPaste: true,

src/components/NetworkSelector.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import React from 'react'
3+
import React, { useMemo, useState, useEffect } from 'react'
44
import { Network } from '@/lib/types/network'
55
import { useTheme } from '@/lib/theme/ThemeProvider'
66
import Card from '@/components/ui/Card'
@@ -19,14 +19,27 @@ export default function NetworkSelector({
1919
onNetworkChange
2020
}: NetworkSelectorProps) {
2121
const { isDarkTheme, setCurrentNetworkId, getColor, getNetworkColor } = useTheme();
22+
const [mounted, setMounted] = useState(false);
2223

23-
// Handle network change
24+
25+
useEffect(() => {
26+
setMounted(true);
27+
}, []);
28+
29+
2430
const handleNetworkChange = (networkId: string) => {
2531
onNetworkChange(networkId);
26-
setCurrentNetworkId(networkId); // Update global theme with network
32+
setCurrentNetworkId(networkId);
2733
};
2834

29-
const selectedNetwork = networks.find(n => n.id === selectedNetworkId);
35+
const selectedNetwork = useMemo(() =>
36+
networks.find(n => n.id === selectedNetworkId),
37+
[networks, selectedNetworkId]
38+
);
39+
40+
if (!mounted) {
41+
return <Card className="space-y-2 h-64"><div className="animate-pulse">Loading networks...</div></Card>;
42+
}
3043

3144
return (
3245
<Card className="space-y-2">
@@ -35,9 +48,9 @@ export default function NetworkSelector({
3548
<div className="grid grid-cols-2 gap-2">
3649
{networks.map((network) => {
3750
const isSelected = selectedNetworkId === network.id;
38-
51+
3952
const networkColor = getNetworkColor('primary');
40-
53+
4154
const buttonStyle = {
4255
backgroundColor: isSelected
4356
? `${networkColor}${isDarkTheme ? '20' : '10'}` // Hex opacity
@@ -91,7 +104,6 @@ export default function NetworkSelector({
91104
})}
92105
</div>
93106

94-
95107
{selectedNetwork && (
96108
<div className="mt-4 text-xs space-y-1">
97109
<div className="flex justify-between">

src/components/Playground.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import NetworkBadge from './ui/NetworkBadge'
1818
import Card from './ui/Card'
1919
import Badge from './ui/Badge'
2020

21-
21+
// Dynamically import components with client-side only rendering
2222
const CodeEditor = dynamic(() => import('./CodeEditor'), { ssr: false })
2323
const Console = dynamic(() => import('./Console'), { ssr: false })
2424
const NetworkSelector = dynamic(() => import('./NetworkSelector'), { ssr: false })
@@ -85,8 +85,9 @@ export default function Playground() {
8585
return colorMap[level] || colorMap['beginner']
8686
}
8787

88+
// SidebarContent component with display name
8889
const SidebarContent = useMemo(() => {
89-
return () => (
90+
const SidebarContentComponent = () => (
9091
<div className="flex flex-col gap-4">
9192
<NetworkSelector
9293
networks={Object.values(NETWORKS)}
@@ -139,6 +140,8 @@ export default function Playground() {
139140
</Card>
140141
</div>
141142
);
143+
SidebarContentComponent.displayName = 'SidebarContent';
144+
return SidebarContentComponent;
142145
}, [
143146
selectedNetwork,
144147
selectedExampleId,
@@ -147,14 +150,17 @@ export default function Playground() {
147150
isRunning,
148151
outputs.length,
149152
runCode,
150-
clearOutput
153+
clearOutput,
154+
setSelectedNetworkId,
155+
setSelectedExampleId
151156
]);
152157

153158
const NetworkIndicator = () => (
154159
<div className="fixed top-0 left-0 right-0 h-1 z-50 opacity-80" style={{
155160
background: `linear-gradient(90deg, ${getNetworkColor()} 0%, rgba(255,255,255,0) 100%)`
156161
}} />
157-
)
162+
);
163+
NetworkIndicator.displayName = 'NetworkIndicator';
158164

159165
const SidebarToggle = () => (
160166
<div className="fixed bottom-6 right-6 lg:hidden z-30">
@@ -182,10 +188,12 @@ export default function Playground() {
182188
</svg>
183189
</button>
184190
</div>
185-
)
191+
);
192+
SidebarToggle.displayName = 'SidebarToggle';
186193

194+
// MainContent component with display name
187195
const MainContent = useMemo(() => {
188-
return () => (
196+
const MainContentComponent = () => (
189197
<div className="flex flex-col gap-4 h-full">
190198
<TutorialPanel example={selectedExample} network={selectedNetwork} />
191199

@@ -225,6 +233,8 @@ export default function Playground() {
225233
)}
226234
</div>
227235
);
236+
MainContentComponent.displayName = 'MainContent';
237+
return MainContentComponent;
228238
}, [
229239
selectedExample,
230240
selectedNetwork,

src/lib/hooks/useLocalStorage.ts

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,33 @@
11
import { useState, useEffect } from 'react';
22

3-
/**
4-
* Hook for using localStorage with type safety
5-
*
6-
* @param key The localStorage key
7-
* @param initialValue The initial value if nothing is in localStorage
8-
* @returns A stateful value and a function to update it
9-
*/
103
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
11-
// State to store our value
124
const [storedValue, setStoredValue] = useState<T>(initialValue);
5+
const [isHydrated, setIsHydrated] = useState(false);
136

14-
// Initialize on first render only
7+
158
useEffect(() => {
16-
try {
17-
// Check if client-side
18-
if (typeof window === 'undefined') {
19-
return;
20-
}
21-
22-
// Get from local storage by key
23-
const item = window.localStorage.getItem(key);
9+
setIsHydrated(true);
2410

25-
// Parse stored json or return initialValue
26-
setStoredValue(item ? JSON.parse(item) : initialValue);
11+
try {
12+
const item = localStorage.getItem(key);
13+
const parsedItem: T = item ? JSON.parse(item) : initialValue;
14+
setStoredValue(parsedItem);
2715
} catch (error) {
28-
// If error, use the initial value
2916
console.error('Error reading from localStorage:', error);
3017
setStoredValue(initialValue);
3118
}
3219
}, [key, initialValue]);
3320

34-
// Function to update stored value
21+
3522
const setValue = (value: T): void => {
3623
try {
37-
// Save state
24+
3825
setStoredValue(value);
3926

40-
// Check if client-side
41-
if (typeof window === 'undefined') {
42-
return;
27+
28+
if (isHydrated) {
29+
localStorage.setItem(key, JSON.stringify(value));
4330
}
44-
45-
// Save to local storage
46-
window.localStorage.setItem(key, JSON.stringify(value));
4731
} catch (error) {
4832
console.error('Error writing to localStorage:', error);
4933
}

0 commit comments

Comments
 (0)