🔴 Redux Cheatsheet Completo 🔴

Redux es una librería de JavaScript para gestionar el estado de la aplicación. Proporciona un “store” centralizado para todo el estado de la aplicación, facilitando la predicción del comportamiento del estado. Es comúnmente utilizado con React, pero es agnóstico a la capa de UI.


1. 🌟 Principios Fundamentales

Redux se adhiere a tres principios básicos:

  1. Única fuente de verdad (Single source of truth): El estado de toda tu aplicación se almacena en un solo objeto dentro de un Store único.
  2. El estado es de solo lectura (State is read-only): La única forma de cambiar el estado es emitiendo una Action, un objeto que describe lo que sucedió. No puedes modificar el estado directamente.
  3. Los cambios se realizan con funciones puras (Changes are made with pure functions): Para especificar cómo el árbol de estado es transformado por las Actions, escribes Reducers puros.

2. 🧩 Componentes Clave


3. 🛠️ Configuración Inicial (Redux Toolkit - ¡Recomendado!)

Redux Toolkit (RTK) es el enfoque recomendado y estándar para escribir lógica Redux. Simplifica el boilerplate y añade buenas prácticas.

  1. Instalación (con React Redux):
    npm install @reduxjs/toolkit react-redux
    # o
    yarn add @reduxjs/toolkit react-redux
  2. Crear un Store (src/app/store.js o store.ts):
    import { configureStore } from '@reduxjs/toolkit';
    import counterReducer from '../features/counter/counterSlice'; // Importa tu reducer de slice
    
    export const store = configureStore({
      reducer: {
        counter: counterReducer, // Cada slice de estado se asigna a una clave
        // ... otros reducers de slices
      },
      // Middleware por defecto de RTK para manejo inmutable y serializable
      // devTools: process.env.NODE_ENV !== 'production', // Habilitar DevTools solo en desarrollo
    });
  3. Proveer el Store a la Aplicación React (src/index.js o main.jsx):
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App.js';
    import { store } from './app/store.js'; // Importa tu store
    import { Provider } from 'react-redux'; // Importa Provider de react-redux
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <React.StrictMode>
        <Provider store={store}> {/* Envuelve tu App con Provider y pásale el store */}
          <App />
        </Provider>
      </React.StrictMode>,
    );

4. 📝 Redux Toolkit (RTK) - Slice de Estado

RTK simplifica Actions, Action Creators y Reducers en un solo concepto: el “Slice”.

4.1. Definir un Slice (src/features/counter/counterSlice.js)

import { createSlice } from '@reduxjs/toolkit';

// Define el estado inicial de este slice
const initialState = {
  value: 0,
};

// Crea un slice de Redux
export const counterSlice = createSlice({
  name: 'counter', // Nombre del slice (se usa como prefijo para los types de acción)
  initialState,    // Estado inicial

  // Reducers: mapeo de nombres de acción a funciones reductoras
  // 'createSlice' usa Immer para permitir "mutar" el estado en los reducers,
  // pero internamente crea nuevas copias inmutables.
  reducers: {
    increment: (state) =&gt; {
      state.value += 1; // Directamente "mutamos" el estado (gracias a Immer)
    },
    decrement: (state) =&gt; {
      state.value -= 1;
    },
    incrementByAmount: (state, action) =&gt; { // Reducer que acepta un payload
      state.value += action.payload;
    },
    reset: (state) =&gt; {
      state.value = 0; // O initialState
    },
  },
});

// Exporta los action creators generados automáticamente
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;

// Exporta el reducer para ser combinado en el store
export default counterSlice.reducer;

// Selectores (opcional, pero buena práctica)
// Se puede definir aquí o en un archivo aparte (ej. selectors.js)
export const selectCounterValue = (state) =&gt; state.counter.value;

5. ⚛️ Interacción con Componentes React (React Redux Hooks)

react-redux proporciona hooks para conectar componentes React al Store de Redux.

5.1. useSelector() - Obtener Estado del Store

import React from 'react';
import { useSelector } from 'react-redux'; // Importa useSelector
import { selectCounterValue } from '../../features/counter/counterSlice'; // Importa tu selector

function CounterDisplay() {
  // Usa useSelector para obtener el valor del contador del estado
  const count = useSelector(selectCounterValue); // Pásale un selector

  // También puedes escribir el selector en línea, pero es menos reutilizable
  // const count = useSelector((state) =&gt; state.counter.value);

  return (
    <div>
      <h3>Valor del Contador: {count}</h3>
    </div>
  );
}
export default CounterDisplay;

5.2. useDispatch() - Enviar Acciones al Store

import React from 'react';
import { useDispatch } from 'react-redux'; // Importa useDispatch
import { increment, decrement, incrementByAmount, reset } from '../../features/counter/counterSlice'; // Importa tus action creators

function CounterControls() {
  const dispatch = useDispatch(); // Obtiene la función dispatch

  return (
    <div>
      <button onClick={() =&gt; dispatch(increment())}>Incrementar</button>
      <button onClick={() =&gt; dispatch(decrement())}>Decrementar</button>
      <button onClick={() =&gt; dispatch(incrementByAmount(5))}>Incrementar en 5</button>
      <button onClick={() =&gt; dispatch(reset())}>Resetear</button>
    </div>
  );
}
export default CounterControls;```

---

## 6. 🌐 Lógica Asíncrona (Redux Thunk - Integrado en RTK)

Para manejar efectos secundarios como llamadas a APIs. Redux Toolkit incluye Redux Thunk por defecto.

### 6.1. Definir un Thunk Asíncrono en un Slice

`createAsyncThunk` genera `Actions` de ciclo de vida (`pending`, `fulfilled`, `rejected`) y maneja la Promesa.

```javascript
// src/features/users/usersSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 1. Define el thunk asíncrono
export const fetchUsers = createAsyncThunk(
  'users/fetchUsers', // Prefijo para los tipos de acción
  async (userId, thunkAPI) =&gt; { // Payload (userId) y thunkAPI (acceso a getState, dispatch, rejectWithValue)
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users${userId ? '/' + userId : ''}`);
      if (!response.ok) {
        // Usa rejectWithValue para pasar un error personalizado al estado 'rejected'
        return thunkAPI.rejectWithValue('Error al obtener usuarios');
      }
      const data = await response.json();
      return data; // El valor de retorno se convierte en action.payload para 'fulfilled'
    } catch (error) {
      return thunkAPI.rejectWithValue(error.message);
    }
  }
);

// Define el slice
const usersSlice = createSlice({
  name: 'users',
  initialState: {
    list: [],
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null,
  },
  reducers: {
    // Reducers síncronos aquí si los hay
  },
  // Extra reducers: para manejar acciones que no están definidas en este slice (ej. acciones de thunks)
  extraReducers: (builder) =&gt; {
    builder
      .addCase(fetchUsers.pending, (state) =&gt; {
        state.status = 'loading';
      })
      .addCase(fetchUsers.fulfilled, (state, action) =&gt; {
        state.status = 'succeeded';
        state.list = Array.isArray(action.payload) ? action.payload : [action.payload]; // Asegura que sea un array
      })
      .addCase(fetchUsers.rejected, (state, action) =&gt; {
        state.status = 'failed';
        state.error = action.payload || action.error.message; // Usa el payload de rejectWithValue o el mensaje de error estándar
      });
  },
});

export default usersSlice.reducer;

// Selector de ejemplo
export const selectAllUsers = (state) =&gt; state.users.list;
export const selectUsersStatus = (state) =&gt; state.users.status;
export const selectUsersError = (state) =&gt; state.users.error;

6.2. Usar un Thunk Asíncrono en un Componente

import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUsers, selectAllUsers, selectUsersStatus, selectUsersError } from '../../features/users/usersSlice';

function UserList() {
  const dispatch = useDispatch();
  const users = useSelector(selectAllUsers);
  const status = useSelector(selectUsersStatus);
  const error = useSelector(selectUsersError);

  useEffect(() =&gt; {
    // Solo si el estado es 'idle', despacha la acción asíncrona
    if (status === 'idle') {
      dispatch(fetchUsers()); // Dispara el thunk
    }
  }, [status, dispatch]); // Dependencias para evitar bucle infinito

  if (status === 'loading') {
    return <div>Cargando usuarios...</div>;
  }
  if (status === 'failed') {
    return <div>Error al cargar usuarios: {error}</div>;
  }

  return (
    <div>
      <h2>Usuarios</h2>
      <ul>
        {users.map((user) =&gt; (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}
export default UserList;

7. 🧰 Redux DevTools Extension

Una extensión de navegador esencial para depurar aplicaciones Redux.


8. 💡 Buenas Prácticas y Consejos


Este cheatsheet te proporciona una referencia completa de Redux, cubriendo sus principios fundamentales, el uso de Redux Toolkit (el enfoque moderno), la interacción con React, la lógica asíncrona y las mejores prácticas para gestionar el estado de forma predecible y eficiente en tus aplicaciones.