⚛️ React

Tutorial Completo de React

Construye interfaces de usuario modernas con componentes, hooks y gestión de estado

Componentes React

Los componentes son los bloques de construcción de React. Pueden ser funcionales o de clase.

JSX
// 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.

JSX
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.

JSX
// 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.

JSX
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.

JSX
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.

JSX
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:

JSX
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>
  );
}