# Redux

## Introducción

Redux es una librería, que nos ayuda a manejar y actualizar estado de nuestra aplicación, he implementa también el patrón [flux](https://shimozurdo.gitbook.io/frontend/principios-de-diseno-de-software/patrones-de-diseno/flux "mention") que nos dice como debemos tratar ese estado y sus actualizaciones.

### Para usarlos se necesita:

* Dónde almacenar: Store
* Cómo acceder: Mediantes métodos, ejempo: selectores.
* Cómo actualizar: Dispachers, actions ó reducers.

### Consta de tres principios:

* Única fuente de verdad (**store**)
* El estado es de solo lectura, no modificarlo directamente sino atraves de **actions**.
* Los cambios deben realizarse através de funciones puras (**reducers**)

La diferencia entre State y Store es que State es un objeto de tipo clave-valor (aunque puede ser de otros tipos). El Store contiene al state y a otras propiedades como los disparadores, suscribers etc..

#### Funciones puras

* Valor retornado cambia si la entrada cambia.
* Misma entrada, misma salida.
* Sin efectos colaterales

#### Reducers

* Calcular el nuevo estado basado en los parámetros (state, action).
* No modificar el estado directamente. (Inmutabilidad)
* No tener lógica asíncrona.

## Ciclo de vida de Redux

<figure><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_N4G6Upo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.imgur.com/riadAin.gif" alt=""><figcaption></figcaption></figure>

<figure><img src="https://624742151-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFRKN8coRWwuuSr24p99X%2Fuploads%2FL4x5cTxWHjcVG5LSgUvz%2Fimage.png?alt=media&#x26;token=a8532e47-55f6-42b1-b289-d8e7a3fbacd8" alt=""><figcaption></figcaption></figure>

### **Diferencias entre Redux y Context API**

**Depuración**

* Redux tiene un depurador que nos permite llevar trazabilidad de como se ha ido comportando nuestro estado.
* Context API al tener todo en el mismo provider y no tener una descripción clara de lo que cambia se más complicado de depurar.

**Bundle size**

* Redux necesita de librerías o paquetes externos y requiere ser manualmente integrado para poder ser utilizado.
* Context API ya viene por defecto integrado y no necesita librerías externas para ser utilizado.

**Middlewares**

* Redux tiene una manera muy simple de extender funcionalidades o acciones con los Middlewares.

**Curva de aprendizaje**

* Redux tiene una curva de aprendizaje mas robusta y al inicio puede ser complicado, se debe saber integrar, aprender un nuevo paradigma y como fluyen los datos con Redux en la aplicación.
* Context API es fácil de aprender y mucho mas ligero.

**Rendering**

* Redux evita render innecesarios.
* Context API no evita estos render innecesarios, sin embargo, hay técnicas para hacerlo pero se deben hacer manualmente.

## Ejemplo Básico

```jsx
import * as Redux from 'https://unpkg.com/redux@latest/dist/redux.browser.mjs'

// Un reducer es una maquina de estados que recive el
// estado y la accion a realizar y devuelve un nuevo estado
function counterReducer(state, action) {
    if (typeof state === 'undefined') {
        return 0;
    }

    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

// Se crea el store
var store = Redux.createStore(counterReducer);

function render() {
    console.log(store.getState().toString());
}

render();

// El subscriber esta escuchando nuevos cambiso al state
store.subscribe(render);

// Despachadores disparan las acciones y las mandan al reducer
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'DECREMENT' });

```

## Ejemplo con React y Connect

```jsx
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { pokemonsReducer } from './reducers/pokemons';
import { Provider } from 'react-redux';
// Cuando no usamos readux toolkit redux sugiere usar legacy_createStore
import { legacy_createStore as createStore } from 'redux';

const root = ReactDOM.createRoot(document.getElementById('root'));

const store = createStore(pokemonsReducer);

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
```

```jsx
// App.js
import { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import PokemonList from './components/PokemonList';
import { getPokemon } from './api';
import { setPokemons as setPokemonsActions } from './actions';

function App({ pokemons, setPokemons }) {
  useEffect(() => {
    const fetchPokemons = async () => {
      const pokemonsRes = await getPokemon(); // api para obtener pokemons
      setPokemons(pokemonsRes);
    };
    fetchPokemons();
  }, []);

  return (
    <div className='App'>
      <PokemonList pokemons={pokemons} /> // Lista de pokemons
    </div>
  );
}

const mapStateToProps = (state) => ({
  pokemons: state.pokemons,
});

const mapDispatchToProps = (dispatch) => ({
  setPokemons: (value) => dispatch(setPokemonsActions(value)),
});

//  Aqui es donde se conecta con el estore de reduc
export default connect(mapStateToProps, mapDispatchToProps)(App);
```

```jsx
// ./actions

// index.js
import { SET_POKEMONS } from './types';

export const setPokemons = (payload) => ({
  type: SET_POKEMONS,
  payload,
});

// types.js
export const SET_POKEMONS = 'SET_POKEMONS';
```

```jsx
// reducers/pokemonsReducers.js

import { SET_POKEMONS } from '../actions/types';

const initialState = {
  pokemons: [],
};

export const pokemonsReducer = (state = initialState, action) => {
  switch (action.type) {
    case SET_POKEMONS:
      return { ...state, pokemons: action.payload };
  default:
      return state;
  }
};
```

### Con hooks useDispatch y useSelector

Cambiamos el archivo App

```jsx
// App.js
import { useEffect, useState } from 'react'
import { connect, useDispatch, useSelector } from 'react-redux'
import PokemonList from './components/PokemonList';
import { getPokemon } from './api';
import { setPokemons } from './actions';

function App() {
  const pokemons = useSelector((state)=>state.pokemons)
  const dispath = useDispatch();
  useEffect(()=>{
    const fetchPokemons = async () => {
      const pokemonsRes = await getPokemon(); // api para obtener pokemons
      dispatch(setPokemons(pokemonsRes));
    };
    fetchPokemons();
  },[])
  return (
    <div className="App">
      <PokemonList pokemons={pokemons} />
    </div>
  )
}

export default App;
```

### hooks vs Connect

* Boilerplate -> Los hooks ahorran mucho código aquí.
* Separación de responsabilidades -> Connect hace un High Order Component, así que es mejor.
* Testing -> Es un poco más fácil con Connect.

## Middlewares

Un middleware es un paso intermedio entre 2 procesos, en el caso de redux los middlewares se ejecutan despues de cuando se dispara una acción y antes de que lleguen al reducer.

### Ejemplo básico

```jsx
// ./middlewares/index.js

// Log
export const logger = (store: any) => {   // store de la aplicación
  return (next: any) => {          // next es una función que se llama cuando el middleware termina su trabajo y envía el action al reducer
    return (action: any) => {      // action es la información que se pasa al reducer
      console.log(action);
      next(action); // <-- hace que el action llegue al reducer
    } 
  }
};

// Alterar o cambiar el estado original antes de que llegue al reducer
export const featuring = (store: any) => {
  return (next: any) => {
    return (actionNew: any) => {
      debugger;
      const featured = [
        { name: 'Momea', url: 'https://ab.c' }, // <-- nuevo pokemon que quiero meter al state
        ...actionNew.payload, // <-- desestructura todos los pokemons que están en el payload de 'action'
      ];
      const updatedAction = {
        ...actionNew,
        payload: [
          ...featured,
        ]
      }
      next(updatedAction);
    } 
  }
};
```

```jsx
// index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { pokemonsReducer } from './reducers/pokemons';
import { Provider } from 'react-redux';
import { applyMiddleware, compose, legacy_createStore as createStore } from 'redux';
import { logger, featuring } from './middlewares';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

const composedEnhancers = compose(
  applyMiddleware(logger, featuring),
  (window as any).__REDUX_DEVTOOLS_EXTENSION__ && (window as any).__REDUX_DEVTOOLS_EXTENSION__(),
)

const store = createStore(pokemonsReducer, composedEnhancers);

root.render(
  <Provider store={store}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </Provider>
);
```

## **Redux Thunk**

Es un Middleware que extiende las capacidades del store (enhancer), nos permite realizar acciones asíncronas en nuestros actions creators que por defecto no podemos hacer

Thunk es un concepto de programación donde se utiliza una función intermedia para retrasar la evaluación o ejecución de una operación, como puede ser la petición de una data a una API y demás acciones

para este caso en particular separaremos responsabilidades, delegando la petición a la API a nuestro action creator y no a el componente como se venia trabajando

***

**Instalación**

```jsx
npm install redux-thunk
```

**Integración** simplemente debemos importarlo y añadirlo a nuestra composición de middlewares src/index.js:

```jsx
import thunk from 'redux-thunk'; 
...
...
...

// index.js
const composeEnhancers = compose( 
  applyMiddleware(thunk, logger)  // **
)

const store = createStore(
    pokemonsReducer,
    composeEnhancers    
  )

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
)
```

si estamos haciendo uso de la herramienta Redux DevTools debemos hacer pasos adicionales src/index.js:

```jsx
import thunk from 'redux-thunk';
...
...

// index.js
const composeAlt = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE_ || compose 

const composeEnhancers = composeAlt( 
  applyMiddleware(thunk, logger) 
)

const store = createStore(
    pokemonsReducer,
    composeEnhancers    
  );

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
)
```

#### **Creación action creator**&#x20;

Una vez integrado procederemos a crear nuestro action creator que será una función la cual contendrá la petición a la API que veníamos trabajando, esa función hará la petición a la API, obtendrá su respuesta y devolverá otra función la cual recibe como parámetro el dispatch para poder disparar acciones, este dispatch recibirá como parámetro la acción setPokemons que a su vez recibirá como parámetro la respuesta de la petición a la API src/actions/index.js:

```js

// src/actions/index.js
export const getPokemonsWithDetails = (pokemons = [])=> async (dispatch)=> { // con el dispatch(next) es con lo que funciona redux thunk
    const pokemonsDetailed = await Promise.all(pokemons.map(pokemon => getPokemonDetail(pokemon)))
    dispatch(setPokemons(pokemonsDetailed))
}
```

#### **Separación de responsabilidades**

Una vez creada el action creator lo importamos y podemos hacer dispatch con el pasándole como parámetro nuestro arreglo de objetos de pokemones src/App.js:

```js
// App.js
const App = () => {
  const pokemons = useSelector(state => state.pokemons)
  const dispatch = useDispatch()


  useEffect(()=> {
    const fetchPokemons = async ()=> {
      const pokemonsRes = await getPokemon()
      dispatch(getPokemonsWithDetails(pokemonsRes)) // llamado a Redux Thunk
    }

    fetchPokemons()
  }, [])

  return (
    <div className="App">
      ....
    </div>
  );
}
```

### Glosario

#### **1. `compose` en Redux**

**`compose`** es una función utilitaria de Redux que permite combinar múltiples funciones en una sola, de manera que cada función recibe como entrada el resultado de la función anterior. Esto se usa comúnmente para combinar varias funciones de **enhancers** en la creación del store.

Es especialmente útil cuando se quieren aplicar múltiples funciones (como `applyMiddleware`, `devToolsExtension`, etc.) en el proceso de creación de un store de Redux. En lugar de hacer llamadas anidadas, `compose` permite encadenarlas de manera más limpia y legible.

**Ejemplo de `compose`:**

```javascript
javascriptCopiar códigoimport { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from './reducers';
import loggerMiddleware from './middlewares/logger';

const enhancer = compose(
  applyMiddleware(loggerMiddleware), // Aplica el middleware
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() // Habilita Redux DevTools si está disponible
);

const store = createStore(rootReducer, enhancer);
```

En este ejemplo, `compose` combina `applyMiddleware` y `Redux DevTools` en una sola función (`enhancer`), que luego se pasa al `createStore`.

**¿Cómo funciona `compose`?**

* Toma funciones de izquierda a derecha.
* Cada función se aplica en el orden en que se pasa a `compose`.
* El resultado de la función de la izquierda se pasa como entrada a la función de la derecha.

**Sintaxis básica**:

```javascript
javascriptCopiar códigoconst composedFunction = compose(func1, func2, func3);
```

* `func1` se ejecuta primero, luego `func2` y finalmente `func3`.

#### **2. `store creator` en Redux**

El **`store creator`** se refiere a la función `createStore` de Redux, que es la que crea y configura el **store** de Redux. El store es donde se mantiene el estado global de la aplicación y donde se gestionan las acciones y el flujo de datos.

**`createStore`** toma tres argumentos:

1. **Reducer**: Es la función que maneja cómo el estado cambia en respuesta a las acciones.
2. **PreloadedState** (opcional): Un estado inicial, es decir, el estado con el que se inicializa el store. Si no se pasa, Redux usará el estado por defecto.
3. **Enhancer** (opcional): Una función (como `applyMiddleware` o `compose`) que extiende o mejora el comportamiento del store.

**Ejemplo de uso de `createStore`:**

```javascript
javascriptCopiar códigoimport { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(
  rootReducer, // El reducer que maneja el estado
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() // Habilita Redux DevTools si está disponible
);
```

#### **Diferencias y Relación entre `compose` y `store creator`**

* **`createStore`** es la función principal utilizada para crear el store de Redux, donde se gestionan todas las acciones y el estado.
* **`compose`** se usa para combinar múltiples funciones (como middlewares o enhancers) que se pasan al `createStore`, de forma que se puedan aplicar de manera ordenada y modular.

#### 3. **Action Creator**

Un **Action Creator** es una función que crea y retorna una acción (un objeto con un tipo específico y opcionalmente algún payload). Los action creators ayudan a encapsular la creación de acciones, permitiendo que sean reutilizables y centralizando la lógica de cada acción. Normalmente, se utilizan para evitar escribir objetos de acción manualmente en los componentes.

* **Ejemplo de un Action Creator**:

  ```javascript
  // Action Creator simple
  const increment = (amount) => {
    return {
      type: 'INCREMENT',
      payload: amount,
    };
  };

  // Usar el Action Creator
  dispatch(increment(5));
  ```

En este ejemplo, `increment` es un action creator que toma un `amount` y devuelve un objeto de acción que luego se puede despachar en el store para ser manejado por los reducers. Esto hace que sea fácil de reutilizar y asegura que las acciones tengan una estructura consistente.

#### 2. **Enhancer**

Un **Enhancer** en Redux es una función que extiende o modifica el store de Redux de alguna manera. Un enhancer no se ocupa de las acciones ni de los datos, sino de cambiar o agregar capacidades al store en sí, como mejorar la forma en que se gestionan las acciones o cómo se observa el estado. Los enhancers se suelen aplicar al crear el store con `createStore`, y se usan comúnmente para integrar herramientas adicionales.

* **Ejemplo de Enhancer**:

  ```javascript
  import { createStore, applyMiddleware, compose } from 'redux';
  import rootReducer from './reducers';
  import loggerMiddleware from './middlewares/logger';

  // Enhancer que combina middlewares y otras herramientas
  const enhancer = compose(
    applyMiddleware(loggerMiddleware), // Agrega middlewares al store
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() // Habilita Redux DevTools
  );

  const store = createStore(rootReducer, enhancer);
  ```

En este ejemplo, `enhancer` utiliza `compose` para combinar `applyMiddleware` (que permite agregar middlewares al store) con Redux DevTools, una herramienta de depuración. Los enhancers, por lo tanto, añaden o modifican capacidades en el store sin cambiar el funcionamiento de las acciones o reducers.

#### Diferencias clave

| Característica       | Action Creator                                              | Enhancer                                   |
| -------------------- | ----------------------------------------------------------- | ------------------------------------------ |
| **Propósito**        | Crear acciones para despachar cambios en el estado.         | Agregar o modificar capacidades del store. |
| **Dónde se utiliza** | Directamente en componentes o dentro de la lógica de Redux. | Al momento de crear el store.              |
| **Ejemplos comunes** | `dispatch(actionCreator())`                                 | `applyMiddleware`, `Redux DevTools`        |
| **Modifica**         | La estructura de las acciones.                              | El comportamiento y capacidades del store. |

### Resumen

Los **action creators** se enfocan en generar acciones específicas para modificar el estado, mientras que los **enhancers** modifican o extienden el store de Redux para agregar capacidades o integraciones adicionales, como middlewares o herramientas de depuración. Ambos son útiles para diferentes aspectos de una aplicación Redux y, en conjunto, contribuyen a un flujo de trabajo escalable y mantenible.

* `createStore` es la función que crea el store de Redux y es responsable de configurar el manejo del estado.
* `compose` es una función de utilidad que permite combinar varias funciones en una sola, facilitando la combinación de enhancers, como middlewares o Redux DevTools, al pasarla a `createStore`.

## Redux Toolkit

Es una libreria que nos ayuda a simplificar el uso de redux

```jsx
npm i @reduxjs/toolkit
```

creamos carpeta&#x20;

```jsx
// ./slice/dataSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  pokemons: [],
  loading: true,
};
export const dataSlice = createSlice({
  name: "data",
  initialState,
  reducers: {
    setPokemons: (state, action) => {
      state.pokemons = action.payload;
      state.loading = false;
    },
    setFavorite: (state, action) => {
      const indexPkmn = state.pokemons.findIndex(
        (pkmn) => pkmn.id == action.payload.id
      );
      if (indexPkmn >= 0) {
        state.pokemons[indexPkmn].isFavorite = !state.pokemons[indexPkmn].isFavorite;
      }
    },
  },
});

export const {setFavorite,setPokemons}= dataSlice.actions

//exportamos por default los reducer del objeto dataSlice para integrarlos en la
//raiz que integra todos nuestros reducers 
export default dataSlice.reducer
```

ahora pasaremos a pasar nustro slice a la raiz de nuestros reducer

```jsx
// ./slice/rootReducer.js
import {combineReducers} from 'redux';
import { pokemonsReducer } from './pokemons';
import dataReducer from '../slices/dataSlice'; 
//como estamos utilicemmos la libreria de immutable, para 
// user reducer combinados, necesitamos instalar redux-immutable
const rootReducer = combineReducers({
    data: dataReducer
})
export default rootReducer;
```

Nota: Falta contenido y mejoras al texto.
