Volver a artículos
Desarrollo de Software

Introducción a React: Construyendo tu Primera App

Aprende React desde cero: componentes, JSX, props, state, hooks básicos y crea tu primera aplicación funcional paso a paso.

Academia Tooldata
30 de agosto de 2026
5 min de lectura
reactjavascriptfrontendcomponentes

¿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 como if o for.

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:

  • classclassName
  • forhtmlFor
  • Atributos en camelCase: onClick, onChange, onSubmit
  • style es 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>&copy; 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

  1. Completa el Weather App y añade más funcionalidades (forecast de 5 días)
  2. Experimenta con props creando componentes reutilizables
  3. Practica useState con diferentes tipos de datos
  4. Crea una Todo List desde cero

Próximos 30 Días

  1. Aprende useEffect para side effects
  2. Explora React Router para navegación
  3. Implementa formularios complejos
  4. 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.

¿Te gustó este artículo?

Explora más contenido educativo en nuestras categorías

Explorar más contenido