Curso react nivel medio

13. Gestores de Estado Externos (Redux y Contexto Avanzado)

Para aplicaciones grandes donde se necesita un estado global más allá del Context API, Redux es una opción popular. Redux organiza el estado de la aplicación en un solo lugar llamado "store", lo que facilita el manejo y sincronización de datos entre múltiples componentes.

1. Introducción a Redux

Redux es una biblioteca para manejar el estado global de una aplicación, basada en tres principios:

  • Store única: Todo el estado de la aplicación se guarda en un solo lugar, el "store".
  • Estado inmutable: El estado no se modifica directamente; en su lugar, se crean copias actualizadas a través de funciones llamadas "reducers".
  • Flujo de datos unidireccional: Las acciones actualizan el estado y provocan que los componentes se vuelvan a renderizar.

Redux maneja el estado mediante tres conceptos clave:

  1. Actions: Son objetos que describen una acción específica (por ejemplo, { type: 'AGREGAR_PRODUCTO', producto: { id: 1, nombre: 'Producto A' } }).
  2. Reducers: Son funciones que reciben el estado actual y una acción, y devuelven el nuevo estado.
  3. Store: Es el almacén único donde se guarda el estado global de la aplicación.

2. Configuración y uso de Redux en una aplicación

Para comenzar a usar Redux, necesitamos instalarlo junto con la integración para React, llamada react-redux:

1npm install redux react-redux

Después, configuramos el store y creamos un reducer para manejar el estado.

Ejemplo básico de configuración de Redux:

1import { createStore } from 'redux';
2import { Provider } from 'react-redux';
3
4// Reducer inicial
5const initialState = { contador: 0 };
6
7function contadorReducer(state = initialState, action) {
8  switch (action.type) {
9    case 'INCREMENTAR':
10      return { ...state, contador: state.contador + 1 };
11    default:
12      return state;
13  }
14}
15
16// Crear el store con el reducer
17const store = createStore(contadorReducer);
18
19function App() {
20  return (
21    <Provider store={store}>
22      {/* Aquí irán los componentes */}
23    </Provider>
24  );
25}
26
27export default App;

En este ejemplo:

  • createStore crea el store global usando contadorReducer como el manejador de estado.
  • Provider envuelve la aplicación, dando acceso al store a todos los componentes.

3. Redux Toolkit y Contexto avanzado

Redux Toolkit es la forma recomendada de usar Redux hoy en día, ya que simplifica la configuración y uso de Redux. Incluye herramientas para escribir reducers y acciones de manera concisa.

Para usar Redux Toolkit:

1npm install @reduxjs/toolkit

Ejemplo de configuración con Redux Toolkit:

1import { configureStore, createSlice } from '@reduxjs/toolkit';
2import { Provider, useDispatch, useSelector } from 'react-redux';
3
4// Crear un slice
5const contadorSlice = createSlice({
6  name: 'contador',
7  initialState: { contador: 0 },
8  reducers: {
9    incrementar: (state) => { state.contador += 1; },
10    decrementar: (state) => { state.contador -= 1; },
11  },
12});
13
14export const { incrementar, decrementar } = contadorSlice.actions;
15
16// Crear el store con configureStore
17const store = configureStore({
18  reducer: {
19    contador: contadorSlice.reducer,
20  },
21});
22
23// Componentes usando dispatch y selector
24function Contador() {
25  const dispatch = useDispatch();
26  const contador = useSelector((state) => state.contador.contador);
27
28  return (
29    <div>
30      <p>Contador: {contador}</p>
31      <button onClick={() => dispatch(incrementar())}>Incrementar</button>
32      <button onClick={() => dispatch(decrementar())}>Decrementar</button>
33    </div>
34  );
35}
36
37function App() {
38  return (
39    <Provider store={store}>
40      <Contador />
41    </Provider>
42  );
43}
44
45export default App;

En este ejemplo:

  • createSlice crea automáticamente el reducer, las acciones y el estado inicial.
  • configureStore configura el store sin necesidad de middleware adicional.
  • useDispatch envía acciones al store, y useSelector obtiene el estado actual del store.

4. Ejercicio práctico: Gestión de una lista de productos con Redux

Creamos una aplicación de lista de productos donde podemos agregar y eliminar productos usando Redux Toolkit.

Configuración del slice de productos (productosSlice.js):

1import { createSlice } from '@reduxjs/toolkit';
2
3const productosSlice = createSlice({
4  name: 'productos',
5  initialState: [],
6  reducers: {
7    agregarProducto: (state, action) => {
8      state.push(action.payload);
9    },
10    eliminarProducto: (state, action) => {
11      return state.filter((producto) => producto.id !== action.payload);
12    },
13  },
14});
15
16export const { agregarProducto, eliminarProducto } = productosSlice.actions;
17export default productosSlice.reducer;

Configuración del store (store.js):

1import { configureStore } from '@reduxjs/toolkit';
2import productosReducer from './productosSlice';
3
4const store = configureStore({
5  reducer: {
6    productos: productosReducer,
7  },
8});
9
10export default store;

Componente de lista de productos (ListaProductos.js):

1import React, { useState } from 'react';
2import { useDispatch, useSelector } from 'react-redux';
3import { agregarProducto, eliminarProducto } from './productosSlice';
4
5function ListaProductos() {
6  const [nombre, setNombre] = useState('');
7  const productos = useSelector((state) => state.productos);
8  const dispatch = useDispatch();
9
10  const manejarAgregarProducto = () => {
11    const nuevoProducto = { id: Date.now(), nombre };
12    dispatch(agregarProducto(nuevoProducto));
13    setNombre('');
14  };
15
16  return (
17    <div>
18      <h1>Lista de Productos</h1>
19      <input
20        type="text"
21        value={nombre}
22        onChange={(e) => setNombre(e.target.value)}
23        placeholder="Nombre del producto"
24      />
25      <button onClick={manejarAgregarProducto}>Agregar Producto</button>
26
27      <ul>
28        {productos.map((producto) => (
29          <li key={producto.id}>
30            {producto.nombre}
31            <button onClick={() => dispatch(eliminarProducto(producto.id))}>Eliminar</button>
32          </li>
33        ))}
34      </ul>
35    </div>
36  );
37}
38
39export default ListaProductos;

Integración en la aplicación principal (App.js):

1import React from 'react';
2import { Provider } from 'react-redux';
3import store from './store';
4import ListaProductos from './ListaProductos';
5
6function App() {
7  return (
8    <Provider store={store}>
9      <ListaProductos />
10    </Provider>
11  );
12}
13
14export default App;

En este ejercicio:

  1. Slice de productos: Creamos un slice que contiene el estado y las acciones agregarProducto y eliminarProducto.
  2. Componente ListaProductos:
    • Usa useDispatch para enviar acciones de agregar y eliminar productos.
    • Usa useSelector para obtener la lista actual de productos.
  3. Store global: Almacena el slice de productos, haciendo que la lista de productos esté disponible globalmente.

Con esta configuración, tenemos una lista de productos completamente funcional gestionada por Redux, lo que permite una mayor escalabilidad y organización del estado global en la aplicación.

  • Loading...