diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
index 5a0f9686..f323e560 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -1,5 +1,5 @@
import { Search, Menu, Wifi, WifiOff, Star, ArrowLeft } from 'lucide-react';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo, useCallback, memo } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { Button } from '@/components/ui/button';
@@ -8,12 +8,19 @@ interface HeaderProps {
onMenuClick: () => void;
}
-const Header = ({ onSearchClick, onMenuClick }: HeaderProps) => {
+// Memoizar iconos para evitar re-renders
+const MenuIcon = memo(() =>
);
+MenuIcon.displayName = 'MenuIcon';
+
+const SearchIcon = memo(() => );
+SearchIcon.displayName = 'SearchIcon';
+
+const Header = memo(({ onSearchClick, onMenuClick }: HeaderProps) => {
const navigate = useNavigate();
const location = useLocation();
// Mostrar botón de retroceso si no estamos en la página principal
- const showBackButton = location.pathname !== '/';
+ const showBackButton = useMemo(() => location.pathname !== '/', [location.pathname]);
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
@@ -29,13 +36,36 @@ const Header = ({ onSearchClick, onMenuClick }: HeaderProps) => {
};
}, []);
- const handleBack = () => {
+ const handleBack = useCallback(() => {
if (window.history.length > 1) {
navigate(-1);
} else {
navigate('/');
}
- };
+ }, [navigate]);
+
+ // Memoizar handlers para evitar re-renders
+ const handleMenuClick = useCallback(() => {
+ // Usar requestAnimationFrame para no bloquear
+ requestAnimationFrame(() => {
+ onMenuClick();
+ });
+ }, [onMenuClick]);
+
+ const handleSearchClick = useCallback(() => {
+ // Usar requestAnimationFrame para no bloquear
+ requestAnimationFrame(() => {
+ onSearchClick();
+ });
+ }, [onSearchClick]);
+
+ // Memoizar clases del estado online
+ const onlineStatusClasses = useMemo(() =>
+ isOnline
+ ? 'bg-success/20 text-success'
+ : 'bg-warning/20 text-warning',
+ [isOnline]
+ );
return (
@@ -83,24 +113,26 @@ const Header = ({ onSearchClick, onMenuClick }: HeaderProps) => {
);
-};
+});
+
+Header.displayName = 'Header';
export default Header;
diff --git a/src/components/layout/MenuSheet.tsx b/src/components/layout/MenuSheet.tsx
index c7709490..3bbb0849 100644
--- a/src/components/layout/MenuSheet.tsx
+++ b/src/components/layout/MenuSheet.tsx
@@ -1,15 +1,23 @@
import { X, Star, History, Settings, Info, Share2, ClipboardCheck, Phone, MessageSquare, BookOpen } from 'lucide-react';
import { Link } from 'react-router-dom';
+import { useMemo, useCallback, memo } from 'react';
+import { toast } from 'sonner';
interface MenuSheetProps {
isOpen: boolean;
onClose: () => void;
}
-const MenuSheet = ({ isOpen, onClose }: MenuSheetProps) => {
+// Memoizar iconos para evitar re-creación
+const MenuIcon = memo(({ Icon, className }: { Icon: any; className?: string }) => (
+
+));
+MenuIcon.displayName = 'MenuIcon';
+
+const MenuSheet = memo(({ isOpen, onClose }: MenuSheetProps) => {
if (!isOpen) return null;
- const handleShare = () => {
+ const handleShare = useCallback(() => {
// Usar setTimeout para no bloquear la UI
setTimeout(async () => {
const shareData = {
@@ -45,19 +53,20 @@ const MenuSheet = ({ isOpen, onClose }: MenuSheetProps) => {
}
}
}, 0);
- };
+ }, [onClose]);
- const menuItems = [
- { icon: , label: 'Manual Completo', path: '/manual', onClick: onClose },
- { icon: , label: 'Protocolos Transtelefónicos', path: '/telefono', onClick: onClose },
- { icon: , label: 'Guiones de Comunicación', path: '/comunicacion', onClick: onClose },
- { icon: , label: 'Checklists Material', path: '/material', onClick: onClose },
- { icon: , label: 'Favoritos', path: '/favoritos', onClick: onClose },
- { icon: , label: 'Historial', path: '/historial', onClick: onClose },
- { icon: , label: 'Compartir App', onClick: handleShare },
- { icon: , label: 'Ajustes', path: '/ajustes', onClick: onClose },
- { icon: , label: 'Acerca de', path: '/acerca', onClick: onClose },
- ];
+ // Memoizar menuItems para evitar re-creación en cada render
+ const menuItems = useMemo(() => [
+ { icon: BookOpen, label: 'Manual Completo', path: '/manual', onClick: onClose },
+ { icon: Phone, label: 'Protocolos Transtelefónicos', path: '/telefono', onClick: onClose },
+ { icon: MessageSquare, label: 'Guiones de Comunicación', path: '/comunicacion', onClick: onClose },
+ { icon: ClipboardCheck, label: 'Checklists Material', path: '/material', onClick: onClose },
+ { icon: Star, label: 'Favoritos', path: '/favoritos', onClick: onClose },
+ { icon: History, label: 'Historial', path: '/historial', onClick: onClose },
+ { icon: Share2, label: 'Compartir App', onClick: handleShare },
+ { icon: Settings, label: 'Ajustes', path: '/ajustes', onClick: onClose },
+ { icon: Info, label: 'Acerca de', path: '/acerca', onClick: onClose },
+ ], [onClose, handleShare]);
return (
<>
@@ -91,7 +100,14 @@ const MenuSheet = ({ isOpen, onClose }: MenuSheetProps) => {
{
+ // Permitir navegación normal, pero ejecutar onClick de forma no bloqueante
+ if (item.onClick) {
+ requestAnimationFrame(() => {
+ item.onClick?.();
+ });
+ }
+ }}
className="w-full flex items-center gap-4 p-4 rounded-xl hover:bg-muted transition-colors text-left"
>
{content}