React tiene fama de tener una curva de entrada complicada, pero la mayoría de esa complejidad viene del ecosistema que lo rodea (routers, gestores de estado, frameworks encima de React) y no de React en sí. El propio React se reduce a una idea sencilla: tu interfaz es una función de tu estado. Cuando el estado cambia, React vuelve a calcular qué debe verse en pantalla.
En este artículo vas a montar un proyecto real desde cero, entender qué es un componente, cómo se le pasan datos con props y cómo se le da memoria con estado. Si todavía no tienes claros los fundamentos de JavaScript frente a otros lenguajes o no sabes usar Git para guardar tu progreso, te conviene repasar antes nuestra guía de Git y GitHub para principiantes, porque la vas a necesitar en cuanto quieras subir este proyecto a un repositorio.
Qué es exactamente un componente
Un componente de React es, en su forma más simple, una función de JavaScript que devuelve algo que se parece a HTML. Esa mezcla de JavaScript y HTML se llama JSX, y no es magia: por debajo se transforma en llamadas a funciones de React que construyen la interfaz. Un componente puede ser tan pequeño como un botón o tan grande como una página entera, y la idea es que se puedan combinar unos dentro de otros.
function Saludo() {
return (
<div className="saludo">
<h2>Hola, soy tu primer componente</h2>
<p>React solo dibuja lo que esta función devuelve.</p>
</div>
);
}
export default Saludo;
Fíjate en el detalle de className en lugar de class: como JSX se compila a JavaScript, hay que usar nombres de atributo que no choquen con palabras reservadas del lenguaje. Es la típica trampa con la que se tropieza todo el mundo la primera semana.
Monta el proyecto con Vite
Durante años la forma estándar de arrancar un proyecto React fue Create React App, pero hoy la opción recomendada por la propia documentación de React es Vite: arranca en segundos, recarga los cambios al instante y no necesita configuración adicional para empezar. Necesitas tener Node.js instalado (lo mismo que usarías para montar un servidor con Express), y con eso ya puedes generar el proyecto:
npm create vite@latest mi-primera-app -- --template react
cd mi-primera-app
npm install
npm run dev
Ese último comando levanta un servidor de desarrollo local, normalmente en http://localhost:5173. Ábrelo en el navegador y verás la plantilla por defecto de Vite. A partir de aquí puedes borrar el contenido de src/App.jsx y empezar desde cero con tus propios componentes.
Props: cómo le pasas datos a un componente
Un componente que siempre muestra lo mismo tiene poca utilidad. Las props (abreviatura de "properties") son la forma en que un componente padre le pasa información a un componente hijo, de forma parecida a como le pasarías argumentos a una función. Son de solo lectura: un componente nunca debería modificar las props que recibe.
function TarjetaProducto({ nombre, precio }) {
return (
<div className="tarjeta">
<h3>{nombre}</h3>
<p>{precio.toFixed(2)} €</p>
</div>
);
}
// Uso: <TarjetaProducto nombre="Teclado mecánico" precio={49.9} />
Cada vez que uses este componente con props distintas, obtienes una tarjeta distinta sin duplicar código. Esa reutilización es el motivo principal por el que React (y los componentes en general) se volvieron el estándar de facto para construir interfaces.
Estado: cómo un componente recuerda cosas
Las props vienen de fuera y no cambian dentro del componente. Pero un componente necesita, a menudo, tener su propia memoria: si un desplegable está abierto o cerrado, cuántas veces se ha pulsado un botón, qué texto ha escrito el usuario en un campo. Para eso existe el estado, y la forma más directa de manejarlo es el hook useState.
Mira la diferencia entre un contador que solo muestra un número fijo y uno que realmente reacciona a los clics del usuario:
function Contador() {
let clics = 0;
function sumar() {
clics = clics + 1;
console.log(clics); // cambia la variable, pero React no lo sabe
}
return (
<div>
<p>Clics: {clics}</p>
<button onClick={sumar}>Sumar</button>
</div>
);
}
// El número en pantalla nunca cambia: React no vuelve
// a renderizar el componente porque no sabe que algo cambió.
import { useState } from "react";
function Contador() {
const [clics, setClics] = useState(0);
function sumar() {
setClics(clics + 1); // avisa a React del cambio
}
return (
<div>
<p>Clics: {clics}</p>
<button onClick={sumar}>Sumar</button>
</div>
);
}
// Cada vez que llamas a setClics, React vuelve a ejecutar
// el componente y actualiza lo que se ve en pantalla.
La diferencia clave es setClics. Modificar una variable normal de JavaScript no hace que React "se entere" de que algo ha cambiado. Solo las funciones actualizadoras que te da useState (o hooks similares como useReducer) disparan un nuevo renderizado. Es la regla más importante que hay que interiorizar al empezar con React, y la fuente de la mayoría de bugs de "mi interfaz no se actualiza" en gente que empieza.
Un proyecto completo: lista de tareas
Con componentes, props y estado ya tienes lo necesario para construir algo real. Una lista de tareas es el ejercicio clásico porque combina las tres cosas: un formulario con estado, una lista que se renderiza a partir de un array, y componentes hijos que reciben props.
import { useState } from "react";
function ListaTareas() {
const [tareas, setTareas] = useState([]);
const [texto, setTexto] = useState("");
function anadirTarea(e) {
e.preventDefault();
if (texto.trim() === "") return;
setTareas([...tareas, { id: Date.now(), texto }]);
setTexto("");
}
return (
<div>
<form onSubmit={anadirTarea}>
<input value={texto} onChange={(e) => setTexto(e.target.value)} />
<button type="submit">Añadir</button>
</form>
<ul>
{tareas.map((t) => (
<li key={t.id}>{t.texto}</li>
))}
</ul>
</div>
);
}
export default ListaTareas;
Fíjate en el key={t.id} dentro del map: React necesita una clave única y estable por cada elemento de una lista para saber cuál cambió, cuál se añadió y cuál se eliminó, en lugar de volver a dibujar la lista entera cada vez. Usar el índice del array como clave funciona en casos simples, pero da problemas en cuanto la lista se reordena o se elimina algo del medio.
No intentes aprender Redux, React Router y Next.js a la vez que aprendes React. Domina componentes, props y estado con proyectos pequeños antes de añadir cualquier capa encima.
Errores típicos al empezar
Hay dos fallos que se repiten constantemente en quien empieza con React. El primero es mutar el estado directamente (tareas.push(nuevaTarea) en lugar de crear un array nuevo con setTareas([...tareas, nuevaTarea])): React compara referencias, así que si mutas el array original no detecta el cambio. El segundo es olvidar que las actualizaciones de estado son asíncronas: si llamas a setClics dos veces seguidas esperando ver el valor duplicado inmediatamente después, te vas a encontrar con comportamientos inesperados hasta que entiendas cómo funciona el ciclo de renderizado.
Con esta base ya puedes seguir ampliando el proyecto: añadir la opción de marcar tareas como completadas, guardar la lista en localStorage, o dividir el formulario y la lista en componentes separados que se comuniquen mediante props. Cada uno de esos pasos es una excusa perfecta para seguir practicando sin necesidad de un proyecto nuevo de cero.