¿Qué es React?
React es una biblioteca de JavaScript desarrollada por Facebook para construir interfaces de usuario (UI) interactivas y dinámicas. Su filosofía central es dividir la UI en componentes reutilizables que se actualizan eficientemente cuando los datos cambian.
Por Qué React es Popular
Ventajas clave:
- Component-based: UI dividida en piezas independientes y reutilizables
- Declarativo: Describes cómo debe verse la UI, React maneja el cómo
- Learn once, write anywhere: React, React Native, React VR
- Ecosistema enorme: Miles de librerías y recursos
- Demanda laboral: Una de las skills más demandadas en frontend
Comparación conceptual:
// Vanilla JavaScript (imperativo)
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', () => {
alert('Clicked!');
});
document.body.appendChild(button);
// React (declarativo)
function App() {
return (
<button onClick={() => alert('Clicked!')}>
Click me
</button>
);
}
Nota: React no es un framework completo (como Angular), es una biblioteca enfocada en la capa de vista. Para routing, estado global, etc., usarás bibliotecas adicionales.
Configuración del Entorno
Opción 1: Create React App (Recomendado para Principiantes)
# Instalar Node.js primero (si no lo tienes): https://nodejs.org
# Crear nueva app
npx create-react-app mi-primera-app
# Navegar al directorio
cd mi-primera-app
# Iniciar servidor de desarrollo
npm start
Esto abrirá http://localhost:3000 con tu app funcionando.
Estructura de archivos:
mi-primera-app/
├── node_modules/ # Dependencias
├── public/
│ └── index.html # HTML base
├── src/
│ ├── App.js # Componente principal
│ ├── App.css # Estilos de App
│ ├── index.js # Punto de entrada
│ └── index.css # Estilos globales
├── package.json # Configuración del proyecto
└── README.md
Opción 2: Vite (Más Rápido)
npm create vite@latest mi-app-vite -- --template react
cd mi-app-vite
npm install
npm run dev
Fundamentos de JSX
JSX (JavaScript XML) es una extensión de sintaxis que permite escribir HTML dentro de JavaScript.
Reglas de JSX
// ✅ Correcto
function Welcome() {
return (
<div>
<h1>Hola Mundo</h1>
<p>Bienvenido a React</p>
</div>
);
}
// ❌ Incorrecto - múltiples elementos raíz
function Welcome() {
return (
<h1>Hola Mundo</h1>
<p>Bienvenido a React</p>
);
}
// ✅ Solución 1: Envolver en div
// ✅ Solución 2: Usar Fragment
function Welcome() {
return (
<>
<h1>Hola Mundo</h1>
<p>Bienvenido a React</p>
</>
);
}
Expresiones en JSX
function Greeting() {
const nombre = "Ana";
const hora = new Date().getHours();
const saludo = hora < 12 ? "Buenos días" : "Buenas tardes";
return (
<div>
<h1>{saludo}, {nombre}!</h1>
<p>Son las {hora}:00</p>
<p>Resultado: {2 + 2}</p>
</div>
);
}
Importante: Dentro de
{}puedes usar cualquier expresión de JavaScript válida, pero no statements comoifofor.
Atributos en JSX
function ImageCard() {
const imageUrl = "https://via.placeholder.com/150";
const altText = "Placeholder image";
return (
<div className="card"> {/* className, no class */}
<img
src={imageUrl}
alt={altText}
style={{ borderRadius: '10px', width: '100%' }} // style es un objeto
/>
<button onClick={() => alert('Clicked!')}>
Ver más
</button>
</div>
);
}
Diferencias importantes:
class→classNamefor→htmlFor- Atributos en camelCase:
onClick,onChange,onSubmit stylees un objeto, no string
Componentes
Componentes Funcionales (Modernos)
// Componente simple
function Header() {
return (
<header>
<h1>Mi Aplicación</h1>
<nav>
<a href="/">Inicio</a>
<a href="/about">Acerca de</a>
</nav>
</header>
);
}
// Componente con lógica
function UserStatus() {
const isLoggedIn = true;
const username = "Carlos";
if (!isLoggedIn) {
return <button>Iniciar Sesión</button>;
}
return (
<div>
<p>Bienvenido, {username}</p>
<button>Cerrar Sesión</button>
</div>
);
}
// Renderizado condicional inline
function Notification({ hasNewMessages }) {
return (
<div>
{hasNewMessages && <span>Tienes mensajes nuevos!</span>}
{hasNewMessages ? (
<button>Ver mensajes</button>
) : (
<p>No hay mensajes nuevos</p>
)}
</div>
);
}
Composición de Componentes
function App() {
return (
<div className="app">
<Header />
<MainContent />
<Footer />
</div>
);
}
function MainContent() {
return (
<main>
<UserStatus />
<Notification hasNewMessages={true} />
<ArticleList />
</main>
);
}
function Footer() {
return (
<footer>
<p>© 2025 Mi Aplicación</p>
</footer>
);
}
Props: Pasando Datos
Props son argumentos que pasas a componentes, similar a parámetros de funciones.
// Definir componente que recibe props
function UserCard(props) {
return (
<div className="user-card">
<img src={props.avatar} alt={props.name} />
<h3>{props.name}</h3>
<p>{props.bio}</p>
<p>Edad: {props.age}</p>
</div>
);
}
// Destructuring (más común)
function UserCard({ avatar, name, bio, age }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{bio}</p>
<p>Edad: {age}</p>
</div>
);
}
// Usar el componente
function App() {
return (
<div>
<UserCard
avatar="https://i.pravatar.cc/150?img=1"
name="Ana García"
bio="Desarrolladora frontend"
age={28}
/>
<UserCard
avatar="https://i.pravatar.cc/150?img=2"
name="Luis Martínez"
bio="Diseñador UX/UI"
age={32}
/>
</div>
);
}
Props por Defecto
function Button({ text = "Haz click", color = "blue", onClick }) {
return (
<button
style={{ backgroundColor: color, color: 'white', padding: '10px 20px' }}
onClick={onClick}
>
{text}
</button>
);
}
// Uso
<Button text="Enviar" color="green" onClick={() => console.log('Enviado')} />
<Button /> {/* Usa valores por defecto */}
Children Prop
function Card({ children, title }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-content">
{children}
</div>
</div>
);
}
// Uso
function App() {
return (
<Card title="Mi Tarjeta">
<p>Este es el contenido de la tarjeta</p>
<button>Acción</button>
</Card>
);
}
State: Manejo de Estado
State es datos que pueden cambiar con el tiempo. Cuando el state cambia, React re-renderiza el componente.
useState Hook
import { useState } from 'react';
function Counter() {
// [valor actual, función para actualizarlo]
const [count, setCount] = useState(0); // 0 es valor inicial
return (
<div>
<p>Has hecho click {count} veces</p>
<button onClick={() => setCount(count + 1)}>
Incrementar
</button>
<button onClick={() => setCount(count - 1)}>
Decrementar
</button>
<button onClick={() => setCount(0)}>
Resetear
</button>
</div>
);
}
Múltiples Estados
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = (e) => {
e.preventDefault();
setIsLoading(true);
setError(null);
// Simular login
setTimeout(() => {
if (username === 'admin' && password === 'password') {
alert('Login exitoso!');
} else {
setError('Usuario o contraseña incorrectos');
}
setIsLoading(false);
}, 1000);
};
return (
<form onSubmit={handleSubmit}>
<h2>Iniciar Sesión</h2>
{error && <p style={{ color: 'red' }}>{error}</p>}
<input
type="text"
placeholder="Usuario"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="Contraseña"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Cargando...' : 'Entrar'}
</button>
</form>
);
}
Estado de Objetos y Arrays
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Aprender React', completed: false },
{ id: 2, text: 'Construir proyecto', completed: false }
]);
const [inputValue, setInputValue] = useState('');
const addTodo = () => {
if (inputValue.trim() === '') return;
const newTodo = {
id: Date.now(),
text: inputValue,
completed: false
};
setTodos([...todos, newTodo]); // Crear nuevo array
setInputValue('');
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
<h1>Lista de Tareas</h1>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="Nueva tarea..."
/>
<button onClick={addTodo}>Agregar</button>
</div>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>Eliminar</button>
</li>
))}
</ul>
<p>Completadas: {todos.filter(t => t.completed).length} / {todos.length}</p>
</div>
);
}
Importante: Nunca modifiques el state directamente. Siempre usa la función setter y crea una copia del estado anterior.
Eventos
Manejo de Eventos Comunes
function EventExamples() {
const [inputValue, setInputValue] = useState('');
// onClick
const handleClick = () => {
console.log('Button clicked!');
};
// onChange
const handleChange = (e) => {
setInputValue(e.target.value);
};
// onSubmit
const handleSubmit = (e) => {
e.preventDefault(); // Prevenir recarga de página
console.log('Form submitted:', inputValue);
};
// onMouseEnter / onMouseLeave
const handleMouseEnter = () => {
console.log('Mouse entered');
};
// onFocus / onBlur
const handleFocus = () => {
console.log('Input focused');
};
return (
<div>
<button onClick={handleClick}>Click me</button>
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleChange}
onFocus={handleFocus}
placeholder="Type something..."
/>
<button type="submit">Submit</button>
</form>
<div
onMouseEnter={handleMouseEnter}
style={{ padding: '20px', background: '#f0f0f0' }}
>
Hover over me
</div>
</div>
);
}
Proyecto Completo: Weather App
Construyamos una aplicación del clima paso a paso:
import { useState } from 'react';
import './App.css';
function App() {
const [city, setCity] = useState('');
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const API_KEY = 'TU_API_KEY'; // Obtener de openweathermap.org
const fetchWeather = async () => {
if (!city.trim()) return;
setLoading(true);
setError(null);
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=es`
);
if (!response.ok) {
throw new Error('Ciudad no encontrada');
}
const data = await response.json();
setWeather(data);
} catch (err) {
setError(err.message);
setWeather(null);
} finally {
setLoading(false);
}
};
const handleSubmit = (e) => {
e.preventDefault();
fetchWeather();
};
return (
<div className="App">
<h1>Aplicación del Clima</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={city}
onChange={(e) => setCity(e.target.value)}
placeholder="Ingresa una ciudad..."
/>
<button type="submit">Buscar</button>
</form>
{loading && <p>Cargando...</p>}
{error && <p className="error">{error}</p>}
{weather && (
<div className="weather-card">
<h2>{weather.name}, {weather.sys.country}</h2>
<div className="temperature">
<img
src={`https://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`}
alt={weather.weather[0].description}
/>
<span>{Math.round(weather.main.temp)}°C</span>
</div>
<p className="description">{weather.weather[0].description}</p>
<div className="details">
<p>Sensación térmica: {Math.round(weather.main.feels_like)}°C</p>
<p>Humedad: {weather.main.humidity}%</p>
<p>Viento: {weather.wind.speed} m/s</p>
</div>
</div>
)}
</div>
);
}
export default App;
Estilos (App.css):
.App {
max-width: 600px;
margin: 50px auto;
padding: 20px;
font-family: Arial, sans-serif;
}
h1 {
text-align: center;
color: #333;
}
form {
display: flex;
gap: 10px;
margin: 20px 0;
}
input {
flex: 1;
padding: 10px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 5px;
}
button {
padding: 10px 20px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.weather-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 15px;
text-align: center;
margin-top: 20px;
}
.temperature {
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
font-weight: bold;
margin: 20px 0;
}
.temperature img {
width: 100px;
height: 100px;
}
.description {
font-size: 20px;
text-transform: capitalize;
margin: 10px 0;
}
.details {
display: flex;
justify-content: space-around;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.3);
}
.error {
color: red;
text-align: center;
padding: 10px;
background-color: #ffe6e6;
border-radius: 5px;
}
Mejores Prácticas
1. Organización de Componentes
src/
├── components/
│ ├── Header.jsx
│ ├── Footer.jsx
│ ├── UserCard.jsx
│ └── Button.jsx
├── pages/
│ ├── Home.jsx
│ └── About.jsx
├── utils/
│ └── helpers.js
├── App.jsx
└── index.js
2. Naming Conventions
// Componentes: PascalCase
function UserProfile() { }
// Variables y funciones: camelCase
const userName = "Ana";
const handleClick = () => { };
// Constantes: UPPER_SNAKE_CASE
const API_URL = "https://api.example.com";
const MAX_RETRIES = 3;
// Event handlers: handle + EventName
const handleSubmit = () => { };
const handleChange = () => { };
const handleClick = () => { };
3. Props Validation (Opcional pero Recomendado)
npm install prop-types
import PropTypes from 'prop-types';
function UserCard({ name, age, email, isActive }) {
return (
<div>
<h3>{name}</h3>
<p>Edad: {age}</p>
<p>Email: {email}</p>
{isActive && <span>Activo</span>}
</div>
);
}
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string,
isActive: PropTypes.bool
};
UserCard.defaultProps = {
email: 'no-email@example.com',
isActive: false
};
4. Evitar Errores Comunes
// ❌ NO: Modificar state directamente
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // NUNCA HACER ESTO
// ✅ SÍ: Crear nueva referencia
setItems([...items, 4]);
// ❌ NO: Olvidar key en listas
{items.map(item => <div>{item}</div>)}
// ✅ SÍ: Incluir key única
{items.map(item => <div key={item.id}>{item.text}</div>)}
// ❌ NO: Usar index como key si el orden puede cambiar
{items.map((item, index) => <div key={index}>{item}</div>)}
Próximos Pasos
Esta Semana
- Completa el Weather App y añade más funcionalidades (forecast de 5 días)
- Experimenta con props creando componentes reutilizables
- Practica useState con diferentes tipos de datos
- Crea una Todo List desde cero
Próximos 30 Días
- Aprende useEffect para side effects
- Explora React Router para navegación
- Implementa formularios complejos
- Conecta con APIs reales
Recursos Adicionales
- Documentación oficial: react.dev (nueva docs)
- Curso interactivo: React Tutorial en react.dev
- Videos: "React JS Crash Course" en YouTube
- Práctica: Frontend Mentor challenges
Continúa Aprendiendo
- Siguiente nivel: "Next.js 14: App Router y Server Components" para aplicaciones en producción
- Complementa con: "Arquitectura de Microservicios con Node.js" para full-stack
Recuerda: React se aprende construyendo. No te quedes solo leyendo - crea proyectos reales, comete errores y aprende de ellos.