diff --git a/src/components/AIChat.tsx b/src/components/AIChat.tsx
index d6c4718..5c59965 100644
--- a/src/components/AIChat.tsx
+++ b/src/components/AIChat.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useCallback, memo } from 'react';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Textarea } from '@/components/ui/textarea';
@@ -25,7 +25,7 @@ export function AIChat({ currentCode }: AIChatProps) {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
- const handleSend = async () => {
+ const handleSend = useCallback(async () => {
if (!input.trim() || isLoading) return;
const userMessage = input.trim();
@@ -61,14 +61,14 @@ export function AIChat({ currentCode }: AIChatProps) {
} finally {
setIsLoading(false);
}
- };
+ }, [input, isLoading, currentCode]);
- const handleKeyDown = (e: React.KeyboardEvent) => {
+ const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
- };
+ }, [handleSend]);
return (
diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx
index 1475c40..3627a6f 100644
--- a/src/components/FileTree.tsx
+++ b/src/components/FileTree.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useCallback, memo } from 'react';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Input } from '@/components/ui/input';
@@ -52,7 +52,7 @@ export function FileTree({
const [draggedId, setDraggedId] = useState(null);
const [dropTargetId, setDropTargetId] = useState(null);
- const toggleFolder = (id: string) => {
+ const toggleFolder = useCallback((id: string) => {
setExpandedFolders((prev) => {
const next = new Set(prev);
if (next.has(id)) {
@@ -62,37 +62,37 @@ export function FileTree({
}
return next;
});
- };
+ }, []);
- const startRename = (file: FileNode) => {
+ const startRename = useCallback((file: FileNode) => {
setEditingId(file.id);
setEditingName(file.name);
- };
+ }, []);
- const finishRename = (id: string) => {
+ const finishRename = useCallback((id: string) => {
if (editingName.trim() && editingName !== '') {
onFileRename(id, editingName.trim());
toast.success('File renamed');
}
setEditingId(null);
setEditingName('');
- };
+ }, [editingName, onFileRename]);
- const handleDelete = (file: FileNode) => {
+ const handleDelete = useCallback((file: FileNode) => {
if (confirm(`Delete ${file.name}?`)) {
onFileDelete(file.id);
toast.success('File deleted');
}
- };
+ }, [onFileDelete]);
- const handleDragStart = (e: React.DragEvent, node: FileNode) => {
+ const handleDragStart = useCallback((e: React.DragEvent, node: FileNode) => {
e.stopPropagation();
setDraggedId(node.id);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', node.id);
- };
+ }, []);
- const handleDragOver = (e: React.DragEvent, node: FileNode) => {
+ const handleDragOver = useCallback((e: React.DragEvent, node: FileNode) => {
e.preventDefault();
e.stopPropagation();
@@ -101,15 +101,15 @@ export function FileTree({
e.dataTransfer.dropEffect = 'move';
setDropTargetId(node.id);
}
- };
+ }, [draggedId]);
- const handleDragLeave = (e: React.DragEvent) => {
+ const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDropTargetId(null);
- };
+ }, []);
- const handleDrop = (e: React.DragEvent, targetNode: FileNode) => {
+ const handleDrop = useCallback((e: React.DragEvent, targetNode: FileNode) => {
e.preventDefault();
e.stopPropagation();
@@ -130,12 +130,12 @@ export function FileTree({
toast.success('File moved');
setDraggedId(null);
setDropTargetId(null);
- };
+ }, [draggedId, onFileMove]);
- const handleDragEnd = () => {
+ const handleDragEnd = useCallback(() => {
setDraggedId(null);
setDropTargetId(null);
- };
+ }, []);
const renderNode = (node: FileNode, depth: number = 0) => {
const isExpanded = expandedFolders.has(node.id);
diff --git a/src/components/SearchInFilesPanel.tsx b/src/components/SearchInFilesPanel.tsx
index d0f4b3f..6558aac 100644
--- a/src/components/SearchInFilesPanel.tsx
+++ b/src/components/SearchInFilesPanel.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useCallback, useMemo } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
@@ -33,7 +33,7 @@ export function SearchInFilesPanel({
const [isSearching, setIsSearching] = useState(false);
const [caseSensitive, setCaseSensitive] = useState(false);
- const searchInFiles = (query: string) => {
+ const searchInFiles = useCallback((query: string) => {
if (!query.trim()) {
setResults([]);
return;
@@ -77,13 +77,13 @@ export function SearchInFilesPanel({
files.forEach(searchNode);
setResults(foundResults);
setIsSearching(false);
- };
+ }, [files, caseSensitive]);
- const handleSearch = () => {
+ const handleSearch = useCallback(() => {
searchInFiles(searchQuery);
- };
+ }, [searchInFiles, searchQuery]);
- const handleResultClick = (result: SearchResult) => {
+ const handleResultClick = useCallback((result: SearchResult) => {
const findFile = (nodes: FileNode[]): FileNode | null => {
for (const node of nodes) {
if (node.id === result.fileId) return node;
@@ -99,7 +99,7 @@ export function SearchInFilesPanel({
if (file) {
onFileSelect(file, result.line);
}
- };
+ }, [files, onFileSelect]);
const highlightMatch = (content: string, start: number, end: number) => {
return (
diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx
index adc73b8..a2f77dd 100644
--- a/src/components/Toolbar.tsx
+++ b/src/components/Toolbar.tsx
@@ -10,7 +10,7 @@ import {
} from '@/components/ui/dropdown-menu';
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List } from '@phosphor-icons/react';
import { toast } from 'sonner';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback, memo } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ThemeSwitcher } from './ThemeSwitcher';
@@ -33,16 +33,16 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
}
}, []);
- const handleCopy = async () => {
+ const handleCopy = useCallback(async () => {
try {
await navigator.clipboard.writeText(code);
toast.success('Code copied to clipboard!');
} catch (error) {
toast.error('Failed to copy code');
}
- };
+ }, [code]);
- const handleExport = () => {
+ const handleExport = useCallback(() => {
const blob = new Blob([code], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
@@ -53,7 +53,7 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast.success('Script exported!');
- };
+ }, [code]);
return (
<>