Por defecto, las acciones en Redux son sincrónicas, lo cual es un problema para una aplicación que necesita interactuar con la API del servidor o realizar otras acciones asincrónicas. Afortunadamente, Redux nos proporciona algo como middleware , que se encuentra entre el despacho de acciones y el reductor. Hay dos de las bibliotecas de middleware más populares para acciones asincrónicas en Redux, estas son Redux Thunk y Redux Saga . En este post consideraremos el primero.Redux Thunk es una biblioteca de middleware que le permite llamar al creador de acciones, devolviendo una función en lugar de un objeto. La función toma el método de despacho como argumento, de modo que después de que se complete la operación asincrónica, úsela para despachar la acción síncrona regular dentro del cuerpo de la función.
Si está interesado, entonces Thunk es un concepto en el mundo de la programación cuando una función se utiliza para retrasar una operación.Instalación y configuración
Primero, agregue el paquete
redux-thunk a su proyecto:
$ yarn add redux-thunk
Luego, agregue middleware cuando cree la
tienda de su aplicación utilizando
applyMiddleware provisto por Redux:
index.js import React from 'react'; import ReactDOM from 'react-dom'; import { createStore, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; import App from './App';
Uso principal
Por lo general, Redux-Thunk se usa para solicitudes asíncronas a una API externa, para recuperar o almacenar datos. Redux-Thunk facilita el envío de acciones que siguen el "ciclo de vida" de una solicitud de API externa.
Por ejemplo, tenemos una aplicación de tareas regular. Cuando hacemos clic en "agregar todo", por lo general, se enviará la primera acción, que informa sobre el comienzo de agregar un nuevo todo. Luego, si el elemento todo es creado y devuelto con éxito por el servidor, se envía otra acción con nuestro nuevo elemento todo, y la operación tiene éxito. Si por alguna razón el servidor devuelve un error, en lugar de agregar un nuevo todo, se muestra una acción con un error que indica que la operación no se ha completado.
Veamos cómo se puede implementar esto usando Redux-Thunk. En el componente, la acción se distribuye como de costumbre:
AddTodo.js import { connect } from 'react-redux'; import { addTodo } from '../actions'; import NewTodo from '../components/NewTodo'; const mapDispatchToProps = dispatch => { return { onAddTodo: todo => { dispatch(addTodo(toto)); } }; }; export default connect( null, mapDispatchToProps )(NewTodo);
En la acción misma, la situación es mucho más interesante. Aquí usaremos la biblioteca
Axios para solicitudes ajax. Si no lo tiene instalado, agréguelo así:
Haremos una solicitud POST a la dirección:
jsonplaceholder.typicode.com/todos :
actions / index.js import { ADD_TODO_SUCCESS, ADD_TODO_FAILURE, ADD_TODO_STARTED, DELETE_TODO } from './types'; import axios from 'axios'; export const addTodo = ({ title, userId }) => { return dispatch => { dispatch(addTodoStarted()); axios .post(`https://jsonplaceholder.typicode.com/todos`, { title, userId, completed: false }) .then(res => { dispatch(addTodoSuccess(res.data)); }) .catch(err => { dispatch(addTodoFailure(err.message)); }); }; }; const addTodoSuccess = todo => ({ type: ADD_TODO_SUCCESS, payload: { ...todo } }); const addTodoStarted = () => ({ type: ADD_TODO_STARTED }); const addTodoFailure = error => ({ type: ADD_TODO_FAILURE, payload: { error } });
Observe cómo nuestro creador de acción
addTodo devuelve una función, en lugar de la acción habitual del objeto. Esta función toma un argumento de despacho de la tienda.
Dentro del cuerpo de la función, primero despachamos la acción sincrónica habitual, que informa que comenzamos a agregar un nuevo todo utilizando una API externa. En palabras simples: la solicitud se envió al servidor. Luego, realizamos una solicitud POST al servidor utilizando Axios. En el caso de una respuesta afirmativa del servidor, despachamos la acción sincronizada utilizando los datos recibidos del servidor. Pero en caso de un error del servidor, enviamos otra acción síncrona con un mensaje de error.
Cuando usamos una API que es realmente externa (remota), como el
JSONPlaceholder en nuestro caso, es fácil notar que hay un retraso hasta que llega la respuesta del servidor. Pero si está trabajando con un servidor local, la respuesta puede llegar demasiado rápido, por lo que no notará un retraso. Entonces, para su conveniencia, puede agregar un retraso artificial al desarrollar:
actions / index.js (pieza de código) export const addTodo = ({ title, userId }) => { return dispatch => { dispatch(addTodoStarted()); axios .post(ENDPOINT, { title, userId, completed: false }) .then(res => { setTimeout(() => { dispatch(addTodoSuccess(res.data)); }, 2500); }) .catch(err => { dispatch(addTodoFailure(err.message)); }); }; };
Y para probar un script con un error, puede lanzar directamente un error:
actions / index.js (pieza de código) export const addTodo = ({ title, userId }) => { return dispatch => { dispatch(addTodoStarted()); axios .post(ENDPOINT, { title, userId, completed: false }) .then(res => { throw new Error('NOT!');
En aras de la exhaustividad, aquí hay un ejemplo de cómo podría verse nuestro reductor de todo para procesar el "ciclo de vida" completo de una solicitud:
reductores / todoReducer.js import { ADD_TODO_SUCCESS, ADD_TODO_FAILURE, ADD_TODO_STARTED, DELETE_TODO } from '../actions/types'; const initialState = { loading: false, todos: [], error: null }; export default function todosReducer(state = initialState, action) { switch (action.type) { case ADD_TODO_STARTED: return { ...state, loading: true }; case ADD_TODO_SUCCESS: return { ...state, loading: false, error: null, todos: [...state.todos, action.payload] }; case ADD_TODO_FAILURE: return { ...state, loading: false, error: action.payload.error }; default: return state; } }
getState
La función devuelta por el creador de la acción asincrónica usando Redux-Thunk también acepta el método
getState como segundo argumento, lo que le permite obtener el estado directamente dentro del creador de la acción:
actions / index.js (pieza de código) export const addTodo = ({ title, userId }) => { return (dispatch, getState) => { dispatch(addTodoStarted()); console.log('current state:', getState());
Al ejecutar este código, el estado actual simplemente se enviará a la consola. Por ejemplo:
{loading: true, todos: Array(1), error: null}
Usar getState puede ser realmente útil cuando necesita reaccionar de manera diferente, dependiendo del estado actual. Por ejemplo, si limitamos el número máximo de elementos a 4, simplemente podemos salir de la función si se excede este límite:
actions / index.js (pieza de código) export const addTodo = ({ title, userId }) => { return (dispatch, getState) => { const { todos } = getState(); if (todos.length >= 4) return; dispatch(addTodoStarted());
Dato curioso : ¿sabía que el código Redux-Thunk consta de solo 14 líneas? Puedes comprobar por ti mismo cómo funciona el middleware Redux-Thunk debajo del capó
Enlace al artículo original:
acciones asincrónicas de Redux con Redux Thunk .