Manual Completo de React Fundamentos hasta Next.js Avanzado

Guía exhaustiva desde los conceptos básicos de React hasta aplicaciones avanzadas con Next.js

Fundamentos React
React Intermedio
React Avanzado
Next.js

1. Fundamentos de React

1.1 Qué es React

React es una biblioteca JavaScript para construir interfaces de usuario. Fue desarrollada por Facebook y se centra en:

1.2 Configuración del Entorno

Terminal
# Crear nueva aplicación con Vite (recomendado)
npm create vite@latest mi-app-react --template react
cd mi-app-react
npm install

# Alternativa con Create React App (legacy)
npx create-react-app mi-app-react
cd mi-app-react

1.3 Estructura Básica de un Componente

src/App.jsx
import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Hola Mundo React</h1>
      <p>Contador: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Incrementar
      </button>
    </div>
  );
}

export default App;

1.4 JSX y Renderizado

JSX es una extensión de sintaxis que permite escribir HTML en JavaScript:

const element = <h1 className="titulo">Hola, mundo!</h1>;

// Se compila a:
const element = React.createElement(
  'h1',
  {className: 'titulo'},
  'Hola, mundo!'
);

1.5 Props y Comunicación entre Componentes

src/components/Saludo.jsx
function Saludo({ nombre, edad }) {
  return (
    <div>
      <h2>Hola {nombre}</h2>
      <p>Tienes {edad} años</p>
    </div>
  );
}

// Uso:
<Saludo nombre="Juan" edad={25} />

1.6 Estado con useState

useState es el hook más básico para manejar estado en componentes funcionales:

import { useState } from 'react';

function Contador() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ nombre: '', edad: 0 });

  return (
    <div>
      <p>Has hecho clic {count} veces</p>
      <button onClick={() => setCount(count + 1)}>
        Haz clic
      </button>
    </div>
  );
}

1.7 Efectos Secundarios con useEffect

useEffect permite manejar efectos secundarios en componentes funcionales:

import { useState, useEffect } from 'react';

function EjemploAPI() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []); // El array vacío significa que se ejecuta solo al montar

  return (
    <div>
      {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : 'Cargando...'}
    </div>
  );
}

2. React Intermedio

2.1 Manejo de Formularios

Formas de manejar formularios en React:

Formularios Controlados

function Formulario() {
  const [formData, setFormData] = useState({
    nombre: '',
    email: '',
  });

  const handleChange = (e) => {
    setFormData({
      ...formData,
      [e.target.name]: e.target.value
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="nombre"
        value={formData.nombre}
        onChange={handleChange}
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      <button type="submit">Enviar</button>
    </form>
  );
}

Con bibliotecas como Formik + Yup

Terminal
npm install formik yup
src/components/FormularioAvanzado.jsx
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';

const validacion = Yup.object().shape({
  nombre: Yup.string()
    .min(2, 'Demasiado corto')
    .required('Requerido'),
  email: Yup.string()
    .email('Email inválido')
    .required('Requerido'),
});

function FormularioAvanzado() {
  return (
    <Formik
      initialValues={{ nombre: '', email: '' }}
      validationSchema={validacion}
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      {({ isSubmitting }) => (
        <Form>
          <Field type="text" name="nombre" />
          <ErrorMessage name="nombre" component="div" />
          
          <Field type="email" name="email" />
          <ErrorMessage name="email" component="div" />
          
          <button type="submit" disabled={isSubmitting}>
            Enviar
          </button>
        </Form>
      )}
    </Formik>
  );
}

2.2 Routing con React Router

Terminal
npm install react-router-dom
src/App.jsx
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/contact">Contact</Link>
      </nav>
      
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<h1>404 Not Found</h1>} />
      </Routes>
    </Router>
  );
}

2.3 Context API para Estado Global

src/context/AuthContext.js
import { createContext, useContext, useState } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (userData) => {
    setUser(userData);
  };

  const logout = () => {
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  return useContext(AuthContext);
}
src/App.jsx
import { AuthProvider } from './context/AuthContext';

function App() {
  return (
    <AuthProvider>
      <Router>
        {/* Resto de la aplicación */}
      </Router>
    </AuthProvider>
  );
}
src/components/Login.jsx
import { useAuth } from '../context/AuthContext';

function Login() {
  const { login } = useAuth();

  const handleLogin = () => {
    login({ name: 'Usuario', email: 'usuario@example.com' });
  };

  return (
    <button onClick={handleLogin}>
      Iniciar sesión
    </button>
  );
}

2.4 Custom Hooks

Creando hooks personalizados para reutilizar lógica:

src/hooks/useLocalStorage.js
import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const storedValue = localStorage.getItem(key);
    return storedValue ? JSON.parse(storedValue) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

export default useLocalStorage;
src/components/ThemeToggle.jsx
import useLocalStorage from '../hooks/useLocalStorage';

function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <button onClick={toggleTheme}>
      Cambiar a tema {theme === 'light' ? 'oscuro' : 'claro'}
    </button>
  );
}

3. React Avanzado

3.1 Gestión de Estado Avanzada

Redux Toolkit (Recomendado)

Terminal
npm install @reduxjs/toolkit react-redux
src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});
src/features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
src/App.jsx
import { Provider } from 'react-redux';
import { store } from './app/store';

function App() {
  return (
    <Provider store={store}>
      <Router>
        {/* Resto de la aplicación */}
      </Router>
    </Provider>
  );
}
src/components/Counter.jsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../features/counter/counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <button onClick={() => dispatch(decrement())}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  );
}

Alternativa: Zustand (Más simple)

Terminal
npm install zustand
src/store/useCounterStore.js
import { create } from 'zustand';

const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

export default useCounterStore;
src/components/CounterZustand.jsx
import useCounterStore from '../store/useCounterStore';

function CounterZustand() {
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

3.2 Renderizado Optimizado

React.memo para memorización de componentes

import { memo } from 'react';

const ListItem = memo(function ListItem({ item }) {
  console.log('Renderizando item:', item.id);
  return <li>{item.name}</li>;
});

function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
}

useMemo para memorización de valores

import { useMemo } from 'react';

function ExpensiveComponent({ list }) {
  const sortedList = useMemo(() => {
    console.log('Ordenando lista...');
    return [...list].sort((a, b) => a.value - b.value);
  }, [list]);

  return <div>{/* Renderizar con sortedList */}</div>;
}

useCallback para memorización de funciones

import { useCallback, useState } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Count:', count);
  }, [count]);

  return <ChildComponent onClick={handleClick} />;
}

const ChildComponent = memo(function ChildComponent({ onClick }) {
  console.log('Renderizando ChildComponent');
  return <button onClick={onClick}>Haz clic</button>;
});

3.3 Pruebas en React

Configuración con Vitest + Testing Library

Terminal
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
vite.config.js
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.js',
  },
});
src/setupTests.js
import '@testing-library/jest-dom';
src/components/__tests__/Counter.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from '../Counter';

describe('Counter', () => {
  it('should increment counter when + button is clicked', () => {
    render(<Counter />);
    const incrementButton = screen.getByText('+');
    const countDisplay = screen.getByText('0');
    
    fireEvent.click(incrementButton);
    
    expect(countDisplay).toHaveTextContent('1');
  });
});

3.4 TypeScript con React

Terminal
npm install -D typescript @types/react @types/react-dom
src/components/TypedComponent.tsx
import { useState } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

interface Props {
  initialUsers: User[];
}

function TypedComponent({ initialUsers }: Props) {
  const [users, setUsers] = useState<User[]>(initialUsers);
  const [newUser, setNewUser] = useState<Omit<User, 'id'>>({ name: '', email: '' });

  const addUser = () => {
    setUsers([...users, { ...newUser, id: Date.now() }]);
    setNewUser({ name: '', email: '' });
  };

  return (
    <div>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} ({user.email})</li>
        ))}
      </ul>
      
      <input
        type="text"
        value={newUser.name}
        onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
        placeholder="Nombre"
      />
      <input
        type="email"
        value={newUser.email}
        onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
        placeholder="Email"
      />
      <button onClick={addUser}>Agregar Usuario</button>
    </div>
  );
}

export default TypedComponent;

4. Next.js: React para Producción

4.1 Introducción a Next.js

Next.js es un framework de React que permite:

4.2 Crear un Proyecto Next.js

Terminal
npx create-next-app@latest mi-app-next
cd mi-app-next
npm run dev

4.3 Sistema de Enrutamiento

Next.js usa un sistema de enrutamiento basado en archivos:

pages/
├── index.js        # Ruta /
├── about.js        # Ruta /about
├── blog/
│   ├── index.js    # Ruta /blog
│   └── [slug].js   # Ruta dinámica /blog/:slug
└── api/           # Rutas API
    └── hello.js    # Ruta /api/hello

Página estática

pages/about.js
function About() {
  return <h1>About Page</h1>;
}

export default About;

Ruta dinámica

pages/blog/[slug].js
import { useRouter } from 'next/router';

function BlogPost() {
  const router = useRouter();
  const { slug } = router.query;

  return <h1>Blog Post: {slug}</h1>;
}

export default BlogPost;

4.4 Pre-renderizado en Next.js

Static Generation (SSG) con getStaticProps

pages/posts.js
function Posts({ posts }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  return {
    props: { posts },
    revalidate: 60, // Re-generar cada 60 segundos (ISR)
  };
}

export default Posts;

Server-side Rendering (SSR) con getServerSideProps

pages/profile.js
function Profile({ user }) {
  return <h1>Perfil de {user.name}</h1>;
}

export async function getServerSideProps(context) {
  const { req, res } = context;
  const userId = req.cookies.userId;
  
  const userRes = await fetch(`https://api.example.com/users/${userId}`);
  const user = await userRes.json();

  return {
    props: { user },
  };
}

export default Profile;

4.5 API Routes

Next.js permite crear endpoints API directamente en tu aplicación:

pages/api/users.js
export default function handler(req, res) {
  if (req.method === 'GET') {
    // Procesar GET
    res.status(200).json([{ id: 1, name: 'John' }]);
  } else if (req.method === 'POST') {
    // Procesar POST
    const { name } = req.body;
    res.status(201).json({ id: 2, name });
  } else {
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

4.6 Autenticación en Next.js

Ejemplo con NextAuth.js (autenticación completa):

Terminal
npm install next-auth
pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';

export default NextAuth({
  providers: [
    Providers.GitHub({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
  ],
  database: process.env.DATABASE_URL,
});
pages/_app.js
import { Provider } from 'next-auth/client';

function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  );
}

export default MyApp;
pages/index.js
import { useSession, signIn, signOut } from 'next-auth/client';

export default function Home() {
  const [session, loading] = useSession();

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      {!session ? (
        <button onClick={() => signIn('github')}>Sign in with GitHub</button>
      ) : (
        <>
          <p>Welcome, {session.user.name}</p>
          <button onClick={() => signOut()}>Sign out</button>
        </>
      )}
    </div>
  );
}

4.7 Despliegue de Aplicaciones Next.js

Configuración para producción

Terminal
npm run build
npm start

Opciones de despliegue:

Despliegue en Vercel

  1. Crear cuenta en vercel.com
  2. Conectar tu repositorio GitHub/GitLab/Bitbucket
  3. Configurar variables de entorno
  4. Deploy automático con cada push