Skip to content

Commit a7338a6

Browse files
shantanu patilclaude
authored andcommitted
perf: fix React re-render issues — stabilize callbacks, memoize components, use refs for animation state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5fffc03 commit a7338a6

4 files changed

Lines changed: 35 additions & 19 deletions

File tree

src/components/Markdown.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ const Markdown: React.FC<MarkdownProps> = ({ content, onDiagramNodeClick }) => {
8888
// Extract and strip diagram data markers from content
8989
const { cleanContent, diagramDataMap } = useMemo(() => extractDiagramData(content), [content]);
9090

91-
// Define markdown components
92-
const MarkdownComponents: React.ComponentProps<typeof ReactMarkdown>['components'] = {
91+
// Define markdown components — memoized to prevent ReactMarkdown full re-renders
92+
const MarkdownComponents: React.ComponentProps<typeof ReactMarkdown>['components'] = useMemo(() => ({
9393
p({ children, ...props }: { children?: React.ReactNode }) {
9494
return <p className="mb-4 text-base leading-7 text-foreground" {...props}>{children}</p>;
9595
},
@@ -261,7 +261,7 @@ const Markdown: React.FC<MarkdownProps> = ({ content, onDiagramNodeClick }) => {
261261
</code>
262262
);
263263
},
264-
};
264+
}), [diagramDataMap, onDiagramNodeClick, explorerUrl]);
265265

266266
return (
267267
<div className="prose prose-base dark:prose-invert max-w-none px-2 py-4">

src/components/landing/Hero3D.tsx

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

3-
import React, { useState, useEffect, useCallback } from 'react';
3+
import React, { useRef, useState, useEffect, useCallback } from 'react';
44
import dynamic from 'next/dynamic';
55
import { motion } from 'framer-motion';
66
import { Environment } from '@react-three/drei';
@@ -21,7 +21,7 @@ interface Hero3DProps {
2121
}
2222

2323
export default function Hero3D({ onSubmit, value, onChange, isSubmitting }: Hero3DProps) {
24-
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
24+
const mousePositionRef = useRef({ x: 0, y: 0 });
2525
const [isMobile, setIsMobile] = useState(false);
2626
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
2727

@@ -45,10 +45,9 @@ export default function Hero3D({ onSubmit, value, onChange, isSubmitting }: Hero
4545

4646
const handleMouseMove = useCallback((e: React.MouseEvent<HTMLElement>) => {
4747
if (prefersReducedMotion) return;
48-
// Normalize mouse position to -1 to 1
49-
const x = (e.clientX / window.innerWidth) * 2 - 1;
50-
const y = -(e.clientY / window.innerHeight) * 2 + 1;
51-
setMousePosition({ x, y });
48+
// Normalize mouse position to -1 to 1 — written to ref to avoid React re-renders
49+
mousePositionRef.current.x = (e.clientX / window.innerWidth) * 2 - 1;
50+
mousePositionRef.current.y = -(e.clientY / window.innerHeight) * 2 + 1;
5251
}, [prefersReducedMotion]);
5352

5453
const animationProps = prefersReducedMotion
@@ -70,7 +69,7 @@ export default function Hero3D({ onSubmit, value, onChange, isSubmitting }: Hero
7069
>
7170
<ambientLight intensity={0.5} />
7271
<pointLight position={[10, 10, 10]} intensity={0.8} />
73-
<KnowledgeCube mouse={mousePosition} />
72+
<KnowledgeCube mouseRef={mousePositionRef} />
7473
<ParticleField />
7574
<Environment preset="city" />
7675
</Canvas>

src/components/landing/KnowledgeCube.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
'use client';
22

3-
import { useRef } from 'react';
3+
import React, { useRef } from 'react';
44
import { useFrame } from '@react-three/fiber';
55
import { Float, Edges, MeshTransmissionMaterial } from '@react-three/drei';
66
import * as THREE from 'three';
77

88
interface KnowledgeCubeProps {
9-
mouse: { x: number; y: number };
9+
mouseRef: React.RefObject<{ x: number; y: number }>;
1010
}
1111

12-
export default function KnowledgeCube({ mouse }: KnowledgeCubeProps) {
12+
export default function KnowledgeCube({ mouseRef }: KnowledgeCubeProps) {
1313
const meshRef = useRef<THREE.Mesh>(null);
1414

1515
useFrame(() => {
1616
if (!meshRef.current) return;
17+
const mouse = mouseRef.current;
1718

1819
// Continuous Y rotation
1920
meshRef.current.rotation.y += 0.003;

src/hooks/useWikiGeneration.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ interface UseWikiGenerationReturn {
6464
isRegenerating: string | null;
6565
}
6666

67+
const CONCURRENCY_BY_PROVIDER: Record<string, number> = {
68+
'google': 5, // Flash 2.5 is fast with high rate limits
69+
'openai': 3, // GPT models have moderate rate limits
70+
'openrouter': 2, // Rate limits vary
71+
'ollama': 1, // Local models are CPU/GPU bound
72+
'bedrock': 3,
73+
'azure': 3,
74+
'dashscope': 2,
75+
};
76+
77+
const DEFAULT_CONCURRENCY = 3;
78+
6779
export function useWikiGeneration(params: UseWikiGenerationParams): UseWikiGenerationReturn {
6880
const {
6981
effectiveRepoInfo,
@@ -96,6 +108,10 @@ export function useWikiGeneration(params: UseWikiGenerationParams): UseWikiGener
96108
const [structureRequestInProgress, setStructureRequestInProgress] = useState(false);
97109
const [isRegenerating, setIsRegenerating] = useState<string | null>(null);
98110

111+
// Ref to track generatedPages without causing callback re-creation
112+
const generatedPagesRef = useRef(generatedPages);
113+
useEffect(() => { generatedPagesRef.current = generatedPages; }, [generatedPages]);
114+
99115
// Template configuration loaded from backend
100116
const templateConfigRef = useRef<TemplateConfig | null>(null);
101117

@@ -125,7 +141,7 @@ export function useWikiGeneration(params: UseWikiGenerationParams): UseWikiGener
125141
const generatePageContent = useCallback(async (page: WikiPage, owner: string, repo: string) => {
126142
return new Promise<void>(async (resolve) => {
127143
try {
128-
if (generatedPages[page.id]?.content) {
144+
if (generatedPagesRef.current[page.id]?.content) {
129145
resolve();
130146
return;
131147
}
@@ -403,7 +419,7 @@ Remember:
403419
setLoadingMessage(undefined);
404420
}
405421
});
406-
}, [generatedPages, currentToken, effectiveRepoInfo, selectedProviderState, selectedModelState, isCustomSelectedModelState, customSelectedModelState, modelExcludedDirs, modelExcludedFiles, language, activeContentRequests, generateFileUrl, modelIncludedDirs, modelIncludedFiles, setError, setLoadingMessage]);
422+
}, [currentToken, effectiveRepoInfo, selectedProviderState, selectedModelState, isCustomSelectedModelState, customSelectedModelState, modelExcludedDirs, modelExcludedFiles, language, activeContentRequests, generateFileUrl, modelIncludedDirs, modelIncludedFiles, setError, setLoadingMessage]);
407423

408424
// Determine the wiki structure from repository data
409425
const determineWikiStructure = useCallback(async (fileTree: string, readme: string, owner: string, repo: string) => {
@@ -940,16 +956,16 @@ IMPORTANT:
940956
setGenerationPhase('generating');
941957
console.log(`Starting generation for ${pages.length} pages with controlled concurrency`);
942958

943-
const MAX_CONCURRENT = 3;
959+
const maxConcurrent = CONCURRENCY_BY_PROVIDER[selectedProviderState] || DEFAULT_CONCURRENCY;
944960
const queue = [...pages];
945961
let activeRequests = 0;
946962

947963
const processQueue = () => {
948-
while (queue.length > 0 && activeRequests < MAX_CONCURRENT) {
964+
while (queue.length > 0 && activeRequests < maxConcurrent) {
949965
const page = queue.shift();
950966
if (page) {
951967
activeRequests++;
952-
console.log(`Starting page ${page.title} (${activeRequests} active, ${queue.length} remaining)`);
968+
console.log(`Starting page ${page.title} (${activeRequests} active, ${queue.length} remaining, concurrency=${maxConcurrent})`);
953969

954970
generatePageContent(page, owner, repo)
955971
.finally(() => {
@@ -962,7 +978,7 @@ IMPORTANT:
962978
setIsLoading(false);
963979
setLoadingMessage(undefined);
964980
} else {
965-
if (queue.length > 0 && activeRequests < MAX_CONCURRENT) {
981+
if (queue.length > 0 && activeRequests < maxConcurrent) {
966982
processQueue();
967983
}
968984
}

0 commit comments

Comments
 (0)