URL: https://www.progressiverobot.com/redux-redux-thunk-pt/

Introdução

Por padrão, as ações do Redux são enviadas de forma síncrona, o que é um problema para todos os aplicativos não triviais que precisam se comunicar com uma API externa ou executar efeitos colaterais. O Redux também permite que middleware fique entre uma ação sendo despachada e a ação que atinge os redutores.

Existem duas bibliotecas de middleware muito populares que permitem efeitos colaterais e ações assíncronas: Redux Thunk e Redux Saga. Neste post, você irá explorar o Redux Thunk.

Thunk (conversão) é um conceito de programação onde uma função é usada para atrasar a avaliação/cálculo de uma operação.

O Redux Thunk é um middleware que permite chamar criadores de ação que retornam uma função em vez de um objeto de ação. Essa função recebe o método de expedição do armazenamento, que é usado então para expedir ações síncronas regulares dentro do corpo da função assim que as operações assíncronas forem concluídas.

Neste artigo, você irá aprender como adicionar o Redux Thunk e como ele pode se encaixar em um aplicativo Todo hipotético.

Pré-requisitos

redux thunk illustration for: Pré-requisitos

Este post assume que você tenha conhecimento básico do React e do Redux. Confira este post se estiver iniciando com o Redux.

Este tutorial é construído a partir de um aplicativo Todo hipotético que rastreia tarefas que precisam ser realizadas e foram concluídas. Assume-se que o create-react-app foi usado para gerar um novo aplicativo React, e o redux, react-redux e axios já foram instalados.

Os detalhes mais finos sobre como criar um aplicativo Todo do zero não serão explicados aqui. Ele será apresentado como um cenário conceitual para evidenciar o Redux Thunk.

Adicionando o redux-thunk

Primeiro, use o terminal para navegar até o diretório do projeto e instale o pacote redux-thunk em seu projeto:

				
					
npm install redux-thunk<^>@2.3.0<^>

				
			

Nota: o Redux Thunk possui apenas 14 linhas de código. Confira aqui o código fonte para aprender sobre como um middleware Redux funciona nos bastidores.

Agora, aplique o middleware ao criar o armazenamento do seu aplicativo usando o applyMiddleware do Redux. Em um dado aplicativo React com redux e react-redux, seu arquivo index.js deve ficar assim:

				
					
[label src/index.js]

import React from 'react';

import ReactDOM from 'react-dom';

import { Provider } from 'react-redux';

import { createStore<^>, applyMiddleware<^> } from 'redux';

<^>import thunk from 'redux-thunk';<^>

import './index.css';

import rootReducer from './reducers';

import App from './App';

import * as serviceWorker from './serviceWorker';



// use applyMiddleware to add the thunk middleware to the store

const store = createStore(rootReducer, <^>applyMiddleware(thunk)<^>);



ReactDOM.render(

 <Provider store={store}>

 <App />

 </Provider>,

 document.getElementById('root')

);

				
			

Agora, o Redux Thunk é importado e aplicado em seu aplicativo.

Usando o Redux Thunk em um aplicativo de amostra

O caso de uso mais comum para o Redux Thunk é para se comunicar de forma assíncrona com uma API externa para recuperar ou salvar dados. O Redux Thunk torna mais fácil expedir ações que seguem o ciclo de vida de uma solicitação para uma API externa.

Criar um novo item de tarefa pendente normalmente envolve primeiro expedir uma ação para indicar que a criação de um item de tarefa pendente foi iniciado. Em seguida, se o item de tarefa for criado com sucesso e retornado pelo servidor externo, expedindo outra ação com o novo item de tarefa. Caso aconteça um erro e a tarefa não seja salva no servidor, uma ação com o erro pode ser expedida em vez disso.

Vamos ver como isso seria feito usando o Redux Thunk.

Em seu componente contêiner, importe a ação e emita-a:

				
					
[label src/containers/AddTodo.js]

import { connect } from 'react-redux';

<^>import { addTodo } from '../actions';<^>

import NewTodo from '../components/NewTodo';



const mapDispatchToProps = dispatch => {

 return {

 <^>onAddTodo: todo => {<^>

 <^>dispatch(addTodo(todo));<^>

 <^>}<^>

 };

};



export default connect(

 null,

 mapDispatchToProps

)(NewTodo);

				
			

A ação irá usar o Axios para enviar uma solicitação POST ao ponto de extremidade em JSONPlaceholder (https://jsonplaceholder.typicode.com/todos):

				
					
[label src/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 como o criador de ação addTodo retorna uma função em vez do objeto de ação regular. Essa função recebe o método de expedição do armazenamento.

Dentro do corpo da função, envia-se primeiro uma ação síncrona imediata para o armazenamento para indicar que iniciou-se o salvamento da tarefa pendente com a API externa. Em seguida, você faz a solicitação POST real ao servidor usando o Axios. No caso de uma resposta bem-sucedida do servidor, você expede uma ação de sucesso síncrona com os dados recebidos da resposta, mas para uma resposta de falha, envia-se uma ação síncrona diferente com a mensagem de erro.

Ao usar uma API externa, como o JSONPlaceholder neste caso, é possível ver o atraso de rede real acontecendo. No entanto, se estiver trabalhando com um servidor de backend local, as respostas de rede podem acontecer muito rapidamente para visualizar o atraso de rede que um usuário real estaria observando. Sendo assim, é possível adicionar um atraso artificial ao desenvolver:

				
					
[label src/actions/index.js]

// ...



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));

 });

 };

};



// ...

				
			

Para testar cenários de erro, emita manualmente um erro:

				
					
[label src/actions/index.js]

// ...



export const addTodo = ({ title, userId }) => {

 return dispatch => {

 dispatch(addTodoStarted());



 axios

 .post(ENDPOINT, {

 title,

 userId,

 completed: false

 })

 .then(res => {

 <^>throw new Error('addToDo error!');<^>

 // dispatch(addTodoSuccess(res.data));

 })

 .catch(err => {

 dispatch(addTodoFailure(err.message));

 });

 };

};



// ...

				
			

Para fins didáticos, aqui está um exemplo de como o redutor de tarefa pendente poderia ser para lidar com o ciclo de vida completo da solicitação:

				
					
[label src/reducers/todosReducer.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;

 }

}

				
			

Explorando o getState

Além de receber o método de expedição do estado, a função retornada por um criador de ação assíncrona com o Redux Thunk também recebe o método getState do armazenamento, de forma que os valores atuais do armazenamento possam ser lidos:

				
					
[label src/actions/index.js]

export const addTodo = ({ title, userId }) => {

 return (dispatch, getState) => {

 dispatch(addTodoStarted());



 <^>console.log('current state:', getState());<^>



 // ...

 };

};

				
			

Com o código acima, o estado atual será impresso no console.

Por exemplo:

				
					
{loading: true, todos: Array(1), error: null}

				
			

Usar o getState pode ser útil para lidar com as coisas de maneira diferente dependendo do estado atual. Por exemplo, se quiser limitar o aplicativo a apenas quatro itens de tarefa por vez, você pode retornar da função se o estado já possuir a quantidade máxima de itens de tarefa:

				
					
[label src/actions/index.js]

export const addTodo = ({ title, userId }) => {

 return (dispatch, getState) => {

 const { todos } = getState();



 <^>if (todos.length > 4) return;<^>



 dispatch(addTodoStarted());



 // ...

 };

};

				
			

Com o código acima, o aplicativo ficará limitado a quatro itens de tarefa.

Conclusão

Neste tutorial, você explorou adicionar o Redux Thunk a um aplicativo React para permitir a expedição de ações de maneira assíncrona. Isso é útil ao usar um armazenamento Redux e APIs externas.

Se quiser aprender mais sobre o React, dê uma olhada em nossa série Como programar no React.js, ou confira nossa página do tópico React para exercícios e projetos de programação.