Componentes React
Los componentes son los bloques de construcción de React. Pueden ser funcionales o de clase.
// Componente funcional (recomendado)
function Saludo({ nombre }) {
return <h1>Hola, {nombre}!</h1>;
}
// Arrow function component
const Boton = ({ texto, onClick }) => {
return (
<button onClick={onClick} className="btn">
{texto}
</button>
);
};
// Uso
function App() {
return (
<div>
<Saludo nombre="Juan" />
<Boton texto="Click aquí" onClick={() => alert('¡Hola!')} />
</div>
);
}
JSX
JSX es una extensión de JavaScript que permite escribir HTML dentro de JavaScript.
function Card({ titulo, descripcion, imagen }) {
const estilos = {
background: '#667eea',
color: 'white'
};
return (
<div className="card" style={estilos}>
{/* Comentarios en JSX */}
<img src={imagen} alt={titulo} />
<h2>{titulo}</h2>
<p>{descripcion}</p>
{/* Expresiones JavaScript */}
<span>Longitud: {titulo.length}</span>
</div>
);
}
Props
Las props permiten pasar datos de un componente padre a un componente hijo.
// Componente hijo
function Usuario({ nombre, edad, email }) {
return (
<div className="usuario">
<h3>{nombre}</h3>
<p>Edad: {edad}</p>
<p>Email: {email}</p>
</div>
);
}
// Componente padre
function ListaUsuarios() {
const usuarios = [
{ id: 1, nombre: 'Ana', edad: 25, email: '[email protected]' },
{ id: 2, nombre: 'Luis', edad: 30, email: '[email protected]' }
];
return (
<div>
{usuarios.map(usuario => (
<Usuario
key={usuario.id}
nombre={usuario.nombre}
edad={usuario.edad}
email={usuario.email}
/>
))}
</div>
);
}
Estado (State)
El estado permite que los componentes tengan datos que pueden cambiar con el tiempo.
import { useState } from 'react';
function Contador() {
// Declarar estado
const [contador, setContador] = useState(0);
const [nombre, setNombre] = useState('');
const incrementar = () => {
setContador(contador + 1);
};
return (
<div>
<p>Contador: {contador}</p>
<button onClick={incrementar}>Incrementar</button>
<input
type="text"
value={nombre}
onChange={(e) => setNombre(e.target.value)}
placeholder="Tu nombre"
/>
<p>Hola, {nombre}!</p>
</div>
);
}
Hooks Principales
Los hooks son funciones que permiten usar estado y otras características de React en
componentes funcionales.
import { useState, useEffect, useRef } from 'react';
function EjemploHooks() {
// useState - estado
const [usuario, setUsuario] = useState({ nombre: '', edad: 0 });
// useRef - referencia
const inputRef = useRef(null);
const enfocarInput = () => {
inputRef.current.focus();
};
return (
<div>
<input
ref={inputRef}
type="text"
value={usuario.nombre}
onChange={(e) => setUsuario({ ...usuario, nombre: e.target.value })}
/>
<button onClick={enfocarInput}>Enfocar</button>
</div>
);
}
useEffect
useEffect permite realizar efectos secundarios como llamadas a APIs o suscripciones.
import { useState, useEffect } from 'react';
function DatosUsuario({ id }) {
const [datos, setDatos] = useState(null);
const [cargando, setCargando] = useState(true);
useEffect(() => {
// Se ejecuta cuando cambia 'id'
async function cargarDatos() {
setCargando(true);
const response = await fetch(`/api/usuarios/${id}`);
const data = await response.json();
setDatos(data);
setCargando(false);
}
cargarDatos();
// Cleanup function
return () => {
// Limpiar suscripciones, timers, etc.
};
}, [id]); // Dependencias
if (cargando) return <p>Cargando...</p>;
return <div>{datos?.nombre}</div>;
}
Ejemplo Real: Banner de Cookies
A continuación, un ejemplo completo de un componente funcional que utiliza hooks para
gestionar un banner de cookies con animaciones y persistencia:
import { useState, useEffect } from 'react';
import { Cookie, X } from 'lucide-react';
export function CookieConsent() {
const [isVisible, setIsVisible] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
// Verificar si el usuario ya ha dado su consentimiento
const consent = localStorage.getItem('cookieConsent');
if (!consent) {
// Pequeño delay para la animación de entrada
setTimeout(() => {
setIsVisible(true);
setIsAnimating(true);
}, 500);
}
}, []);
const handleAccept = () => {
localStorage.setItem('cookieConsent', 'accepted');
localStorage.setItem('cookieConsentDate', new Date().toISOString());
closeConsent();
};
const handleReject = () => {
localStorage.setItem('cookieConsent', 'rejected');
localStorage.setItem('cookieConsentDate', new Date().toISOString());
closeConsent();
};
const closeConsent = () => {
setIsAnimating(false);
setTimeout(() => {
setIsVisible(false);
}, 300);
};
if (!isVisible) return null;
return (
<div className={`fixed bottom-0 left-0 right-0 z-50 p-4 transition-all duration-300 ease-out ${
isAnimating ? 'translate-y-0 opacity-100' : 'translate-y-full opacity-0'
}`}>
<div className="mx-auto max-w-7xl">
<div className="relative bg-white dark:bg-gray-900 rounded-lg shadow-2xl border border-gray-200 dark:border-gray-700 p-6">
{/* Botón de cerrar */}
<button onClick={closeConsent} className="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors">
<X className="size-5" />
</button>
<div className="flex flex-col md:flex-row md:items-center gap-4">
<div className="bg-blue-100 dark:bg-blue-900/30 rounded-full p-3">
<Cookie className="size-6 text-blue-600 dark:text-blue-400" />
</div>
<div className="flex-1">
<h3 className="font-semibold text-lg mb-2">🍪 Utilizamos cookies</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Mejoramos tu experiencia de navegación y analizamos el tráfico. Puedes aceptar todas o rechazarlas.
</p>
</div>
<div className="flex gap-3">
<Button onClick={handleReject} variant="outline">Rechazar</Button>
<Button onClick={handleAccept} className="bg-blue-600 text-white">Aceptar todas</Button>
</div>
</div>
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 rounded-b-lg" />
</div>
</div>
</div>
);
}