DEV Community

Cover image for Introducción react-redux y redux toolkit
leobar37
leobar37

Posted on

Introducción react-redux y redux toolkit

Después de haber entendido algunos conceptos de Redux, llegó el momento de utilizarlo como tal 😁.

Setup

Para hacer el setup de la app usaré vite. Vite es una alternativa a webpack, el cual mejora la experiencia de usuario y es mucho más rápido. Si quieres saber más acerca de los beneficios de vite, puedes visitar el siguiente artículo

Para crear una aplicación con vite y react, solo es necesario abrir tu consola y poner el siguiente comando.

yarn create vite redux-tutorial --template react-ts

Enter fullscreen mode Exit fullscreen mode

La opción de --template le dice a vite con que template inicializar el proyecto, en este caso el de react-ts ahora ya se tendrá la siguiente estructura.

file structure

Ahora empecemos con las dependencias, como habíamos dicho utilizaremos redux con react, para eso tenemos que instalar el paquete react-redux, el cual trae lo necesario para hacer la conexión a redux, además a ellos vamos a instalar @reduxjs/toolkit el cual trae algunos superpoderes para redux

yarn add react-redux @reduxjs/toolkit
Enter fullscreen mode Exit fullscreen mode

¿Que es redux toolkit?

Redux esta bien, pero era un poco complicado. Actualmente, contamos con Redux toolkit él ofrece las siguientes soluciones:

  • Simplifica la configuración de redux
  • Elimina la necesidad de agregar múltiples paquetes para tener una aplicación escalable.
  • Reduce el código repetitivo.

Actualmente no se recomienda usar react-redux sin @reduxjs/toolkit.

Preparando el Store

Para empezar a escribir lógica con redux, lo primero que se tiene que hacer es configurar el Store. Para eso Redux toolkit provee un método que nos ayuda con el procedimiendo, el cual se llama configureStore.

// store/index.ts

import { configureStore } from "@reduxjs/toolkit";

export const store = configureStore({
  reducer: {},
  devTools: process.env.NODE_ENV !== "production",
});

export default store;

Enter fullscreen mode Exit fullscreen mode

configureStore

Ahora ya tenemos él store :) . Al hacer esto redux-toolkit ha puesto algunas configuracioens por defecto, las cuales iré comentando conforme avancemos en el ejemplo. En este instante podemos hablar de las devTools las cuales son indispensables para poder depurar la aplicación. En este caso la opción devtools se activa solo en producción, también puedes personalizar el funcionamiento, pasando un objeto de opciones.

Conexión con React

Ahora es momento de poner disponible el store hacia React, para eso react-redux provee un Provider para poner disponible el Store en todo el árbol de componentes.

import "./App.css";
import { Provider as ReduxProvider } from "react-redux";
import store from "./store";

function App() {
  return (
    <ReduxProvider store={store}>
      <div></div>
    </ReduxProvider>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Estructura de archivos en Redux

Ahora que ya se tiene la tienda en el nivel superior de la aplicación, es hora la lógica de nuestra aplicación, en este caso vamos a hacer una agenda de contactos, para poder realizar un CRUD. Antes de continuar necesitamos tener en cuenta algo muy importante, el cual es la estructura de los archivos. Si bien React es una librería muy flexible frente a la estructura de archivos, Redux poner a nuestra disposición una estructura base, para a partir de eso organizar nuestros archivos.

Pensando en Ducks

Ducks es una propuesta que básicamente propone que empaquetemos un conjunto de acciones, reductores, nombres de acciones a una funcionalidad en concreto, llamando a esta agrupación duck el cual, tranquilamente, puede empaquetarse y distribuirse como una librería.

Ahora, teniendo un poco en cuenta los patos 😅, vamos a dividir la aplicación en algo parecido, pero le llamaremos features. De esta manera.

Modules

Feature Structure:

Como se mencionaba en un inicio, el ecosistema de React es muy flexible a la hora de organizar los archivos. Teniendo en cuenta los elementos de redux, al momento de partir una feature debemos dividir actions, reducer, selectors esto mejora la organización.

En mi caso inspirado un poco en el siguiente artículo, mi estructura es la siguiente.

modules

View: Carpeta donde van las vistas que el usuario va a ver en pantalla, generalmente todos los componentes que son utilizando junto con el router de la aplicación. Por ejemplo, si estamos creando una aplicación de inventarios, el listado de estos productos podrían ir en una pantalla producs/list.

Componentes: Normalmente, se querrá tener una carpeta components en general, donde estén ubicados todos aquellos componentes, que pueden ser usados en cualquier lugar, una feature puede tener componentes que sean propios de la característica, por ejemplo el listado, de productos.

actions: En esta carpeta irán todas las acciones ligadas a esta característica.

reducer: Cada feature tiene como regla que debe exportar un solo reductor, eso no quiere decir que solo tengamos que concentrar toda lógica en un solo reducer, podemos usar combineReducers para combinar múltiples reducer en unos solo si es que fuese necesario.

Puedes pensar en una feature como una mini aplicación dentro de una aplicación, este se encarga de un proceso en concreto, que al final aportará un valor agregado a la aplicación en general.

Accciones

Las acciones son objetos planos que expresan una intención de cambiar el estado, eso es lo que se mencionó en el artículo anterior. Puedes pensar en una acción como un evento ocurrido en la aplicación, por ejemplo; se agregó un producto, se eliminó un contacto, cargando contactos, todos ellos describen algo que está pasando en al app.

Dicho esto podemos empezar a escribir acciones, las acciones tiene un estándar que indican que deben ser así.

{
  type: 'ADD_TODO',
  payload: {
    text: 'Do something.'
  }
}
Enter fullscreen mode Exit fullscreen mode

Creadores de acciones:

Normalmente, las acciones puede despacharse de la siguiente manera.

store.dispatch({ type: "ITEM_ADDED_TO_CART", payload: 47 });
Enter fullscreen mode Exit fullscreen mode

Pero en cierto punto, poner el tipo, cada vez que queramos despachar esta acción, no es muy escalable porque si se quisiera cambiar el tipo de acción, tendría que hacerlo en diferentes archivos y además, se vuelve complicado repetir lo mismo.

Ahí es donde entran loas creadores de acciones, que no son nada más que funciones encargadas de crear este objeto, un creador de acción sería el siguiente.

function doAddToDoItem(text) {
  return { type: "TODO_ADDED", payload: text };
}
Enter fullscreen mode Exit fullscreen mode

Entonces, cada vez que se requiera formar esta acción, solo es necesario ejecutar doAddToDoItem.

Redux toolkit Simplifica este procedimiento con un utility llamado createAction el cual es una HOF(higher order function) los cuales son funciones que retornan funciones.

// features/schedule/actions/schedule.actions.ts
import { createAction } from "@reduxjs/toolkit";

export const contactAdded = createAction("CONTACT_ADDED");
Enter fullscreen mode Exit fullscreen mode

Ahora contactAdded es una función que al ser disparada creará una acción de tipo CONTACT_ADDED es importante saber que por recomendación de redux las acciones deben ser "Descripciones de eventos que ocurrieron" en lugar expresarlas en presente, como por ejemplo ADD_CONTACT.

Payload:

Hasta este paso se creó la acción contactAdded, pero esto no es suficiente para agregar un contacto, se necesitaría la información de ese contacto. En el caso de typescript redux toolkit tiene un genérico para poder describir el payload.

import { createAction } from "@reduxjs/toolkit";

export const contactAdded =
  createAction<{ name: string; phone: string }>("CONTACT_ADDED");
Enter fullscreen mode Exit fullscreen mode

Listo ahora el primer parámetro(payload) de contactAdded estará tipado.

Reducer

Como se mencionó anteriormente, los reducers son funciones puras que toman el estado actual y la acción para retornar un nuevo estado.

Redux toolkit exporta una función llamada createReducer la cual facilita la creación de un reducer agregando ciertas características que facilitan el desarrollo.

import { createReducer } from "@reduxjs/toolkit";

const initalState = {
  contacts: [],
};

export type ScheduleState = typeof initalState;

const reducer = createReducer(initalState, (builder) => {});
Enter fullscreen mode Exit fullscreen mode

Esta sería la forma de crear un reducer con Redux toolkit

Case:

Anteriormente vimos, que cuando creamos un reducer dentro de el planteamos un switch...case para manejar cada acción.

const reducer = (state, action) => {
  switch (action) {
    case "EAT": {
      return {
        ...state,
        eatCount: state.eatCount + 1,
      };
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Redux toolkit propone una forma más amigable de hacerlo, mediante un objeto builder, el cual expone una serie de métodos como addCase con el cual le recibe como parámetros.

ActionCreator: La función generada por createActiono una acción como tal.

Reducer: El reducer encargado solo de manejar esta acción.

Incorporando la lógica de agregar contacto, tendríamos lo siguiente.

import { createReducer } from "@reduxjs/toolkit";
import * as scheduleActions from "../actions/schedule.actions";

export interface IContact {
  id: number;
  name: string;
  phone: string;
}
const initalState = {
  contacts: [] as IContact[],
};

export type ScheduleState = typeof initalState;

const reducer = createReducer(initalState, (builder) => {
  builder.addCase(scheduleActions.contactAdded, (state, action) => {
    state.contacts.push({
      id: state.contacts.length,
      name: action.payload.name,
      phone: action.payload.phone,
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Si eres curioso. La manera en como builder encadena todos los casos sigue fluent Style😶 .

Hay algo notable aquí, y es que al parecer no estamos siguiendo el primer principio de Redux, que dice que el estado es de solo lectura, ose es inmutable. Bueno podemos ahorrarnos esa preocupación con Immer, el cual explicaré en el siguiente parte :).

Happy Coding😄

Top comments (0)