Skip to content

Estructura de aplicaciones React ​

Patrones de organización para componentes React. Cómo estructurar código para que escale sin volverse un caos.

Estructura de carpetas ​

Mi estructura actual para proyectos React:

src/
├── components/       # Componentes reutilizables
│   ├── Button.jsx
│   ├── Button.css
│   ├── Card.jsx
│   └── Card.css
├── pages/            # Vistas completas (rutas)
│   ├── HomePage.jsx
│   ├── HomePage.css
│   ├── LoginPage.jsx
│   └── LoginPage.css
├── context/           # Estado global
│   └── AuthContext.jsx
├── hooks/             # Lógica reutilizable
│   ├── useAuth.js
│   └── useFetch.js
├── services/          # Llamadas API
│   └── api.js
├── utils/             # Funciones auxiliares
│   └── formatDate.js
├── App.jsx
├── App.css
├── index.css
└── main.jsx

Componentes presentacionales vs containers ​

Presentacionales: solo renderizan, no tienen lógica de negocio.

jsx
// components/Button.jsx
const Button = ({ children, onClick, variant = 'primary' }) => {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {children}
    </button>
  );
};

Containers: conectan con datos, manejan estado.

jsx
// containers/GameList.jsx
const GameList = () => {
  const [games, setGames] = useState([]);

  useEffect(() => {
    api.get('/games').then(setGames);
  }, []);

  return <div className="games-grid">{games.map(g => <GameCard key={g.id} game={g} />)}</div>;
};

Custom Hooks para lógica reutilizable ​

Extraer lógica a hooks mantiene los componentes limpios.

Ejemplo: useAuth ​

javascript
// hooks/useAuth.js
import { useContext } from 'react';
import { AuthContext } from '../context/AuthContext';

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

// Uso en cualquier componente
const GameCard = ({ game }) => {
  const { user, logout } = useAuth();
  return <div>...</div>;
};

Ejemplo: useFetch ​

javascript
// hooks/useFetch.js
import { useState, useEffect } from 'react';

export const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network error');
        setData(await response.json());
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [url]);

  return { data, loading, error };
};

Props vs Context vs Estado local ​

Cuándo usarEjemplo
Estado local (useState)Toggle de un modal, input de un formulario
PropsDatos que vienen del padre, callbacks
ContextAuth global, tema (dark/light), lenguaje
Estado en URL (useParams)ID del recurso /games/:id

Regla general: no uses Context para todo. Si solo un componente necesita un valor, props basta.

Composición vs Prop drilling ​

Prop drilling (evitar en casos profundos) ​

jsx
// App.jsx
<UserProvider>
  <App>
    <Dashboard>
      <Sidebar>
        <UserAvatar />  // Solo para mostrar nombre del usuario
      </Sidebar>
    </Dashboard>
  </App>
</UserProvider>

Composición (mejor) ​

jsx
// Componente que recibe children
const Layout = ({ children }) => {
  return <div className="layout">{children}</div>;
};

// Uso: composición en vez de props innecesarios
<Layout>
  <Sidebar>
    <UserAvatar user={user} />
  </Sidebar>
</Layout>

Servicios API centralizados ​

En vez de hacer fetch dispersos por toda la app:

javascript
// services/api.js
const API_URL = import.meta.env.VITE_API_URL;

export const api = {
  get: (path) => fetch(`${API_URL}${path}`).then(r => r.json()),
  post: (path, body) => fetch(`${API_URL}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  }).then(r => r.json()),
  delete: (path) => fetch(`${API_URL}${path}`, { method: 'DELETE' }).then(r => r.ok)
};

// Uso
const games = await api.get('/games');
await api.post('/games', { title: 'New Game' });

Patrones que evitar ​

No: todo en App.jsx ​

jsx
// ❌ NO
const App = () => {
  const [user, setUser] = useState(null);
  const [games, setGames] = useState([]);
  const [theme, setTheme] = useState('dark');
  // 500 líneas de lógica...
};

Sí: separar responsabilidades ​

jsx
// ✅ SÍ
const App = () => {
  return (
    <AuthProvider>
      <ThemeProvider>
        <Router>
          <Routes>
            <Route path="/games" element={<GamesPage />} />
          </Routes>
        </Router>
      </ThemeProvider>
    </AuthProvider>
  );
};

Archivos de barril (index.js) ​

Exportar todo desde un archivo central para imports limpios:

javascript
// hooks/index.js
export { useAuth } from './useAuth';
export { useFetch } from './useFetch';

// Uso: un solo import
import { useAuth, useFetch } from '../hooks';

La estructura de React no es dogma—adáptala a tu proyecto. Un proyecto de 3 componentes no necesita 6 carpetas. Pero cuando crece, una buena estructura paga dividends.

Más guías y proyectos en mmoreno.dev.