Handling async operations and using redux-thunk inside react application.

This post is in continuation of my previous post on integrating react with redux.

learnwebtechs.com/2018/08/19/integrating-redux-and-react

Problem:  How and where to handle async operations inside react redux application.

Solution: Async operations should be handled inside the action creators since reducers are pure functions and it should be free from any side effects.

Let's handle the async operation first without any external library or dependency.

Let's update the App.js  and go through its changes.

App.js

import React, { Component } from 'react';
import { connect } from 'react-redux';


import './App.css';

const actions = {
  ADD:"ADD",
  REMOVE:"REMOVE"
}


let addMessage = (username,message,dispatch) => {
 setTimeout(() => {
    dispatch({
      type:actions.ADD,
      payload: {
        message:message,
        username:username,
        id:Date.now()
      }
     })
  },2000)
}


let removeMessage = (id,dispatch) => {
 setTimeout(() => {
  dispatch({
    type:actions.REMOVE,
    payload:id
  })
 },2000)
}

class App extends Component {

  constructor(props){
    super(props);
    this.state = {
      username:"",
      message:""
    }
  }

  onUsernameChange = (event) => {
   event.preventDefault();
   let username = event.target.value;
   this.setState(() => {
     return {
       username: username
     }
   })
  } 

  onMessageChange = (event) => {
    event.preventDefault();
    let message = event.target.value;
    this.setState(() => {
      return {
        message: message
      }
    })
  }

  
  onSubmit = (event) => {
    event.preventDefault();
    let message = this.state.message;
    let username = this.state.username;
    this.props.add(message,username);

  }

  render() {
    return (
      <div className="wrapper">
        <form name="post" onSubmit={this.onSubmit}>
        <input style={{padding:'10px',margin:'10px',width:'70%',textAlign:'left'}}
         type="text" 
         placeholder="username" 
         name="username" value={this.state.username}
         onChange={this.onUsernameChange} /><br />

        <textarea style={{padding:'10px',margin:'10px',width:'70%',rows:'50',cols:'40',textAlign:'left'}} 
        placeholder="message"
        name="message" 
        value={this.state.message}
        onChange={this.onMessageChange}>
         </textarea><br />

         <input style={{padding:'10px',margin:'10px',backgroundColor:'orange',textAlign:'left'}}
          type="submit"
           value="Post" />
        </form>
        <div>
          {this.props.messages.map((message) => {
            return <div style={{display:'inline-block',
            padding:'20px',cursor:'pointer', margin:'0 20px 20px 0',
             border:'1px solid black', width:'200px'}} key={message.id}
              onClick={() => this.props.remove(message.id)}>
              <p>Username: {message.username}</p>
              <p>Message: {message.message}</p>
            </div>
          })}
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    messages: state.messageList
  }
}

const mapDispatchToProps = dispatch => {
return {
 add : (username,message) => addMessage(username,message,dispatch),
 remove: (id,message) => removeMessage(id,dispatch) 
}
}


export default connect(mapStateToProps,mapDispatchToProps)(App);

 

#13 defines a function addMessage with 3 parameters, the most interesting one is the last parameter named dispatch. Now we have the dispatch function to fire once the async operation is done. Here setTimeout() is used to simulate the async operation with a delay of 2 milliseconds.

#118 defines a special function which attaches or maps action creators to component props and it has access to special dispatch function passed as the first argument to it through connect function. Each action creator is passed the dispatch function as an argument to have a handle over the async operation.

GitHub link:

https://github.com/krishna28/react-redux-async-thunk/tree/async-without-redux-thunk

The problem with this approach is that we need to explicitly pass the extra dispatch function to every action creator as an argument.

Let's move ahead with the handling of async operation using redux-thunk as a library.

Step 1. Install the package redux-thunk.

npm install redux-thunk --save

 

Step 2. Update index.js file as mentioned below and let's go through the changes.

 

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 App from './App';

const actions = {
  ADD:"ADD",
  REMOVE:"REMOVE"
}
  
  let initialState = {
    messageList:[]
  }

  
  let rootReducer = (state = initialState,action) => {
  
    switch(action.type){
      case actions.ADD:
       return {...state,
         messageList:state.messageList.concat(action.payload)
        }
      case actions.REMOVE:    
       return {...state, 
        messageList: state.messageList.filter(item => item.id !== action.payload)
      }
    }
    return state;
  
  }

let store = createStore(rootReducer,applyMiddleware(thunk));
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));

 

#4 imports another special function named applyMiddleware from redux used to enhance the store.

#5 imports redux-thunk for handling async operations.

#36 initializes the store with the second argument as applyMiddleware function passing in thunk as an argument. We can pass more arguments to applyMiddleware to enhance the store. Middleware is a special function which executes on every action and can help to log events inside the react redux application. Logger is a good use case for middleware. Its similar to middleware concept used inside the express.js application.

Step 3. Update App.js file as mentioned below and let's go through the changes.

 

import React, { Component } from 'react';
import { connect } from 'react-redux';


import './App.css';

const actions = {
  ADD:"ADD",
  REMOVE:"REMOVE"
}


let addMessage = (username,message) => {
  return dispatch => {
    setTimeout(() => dispatch({
        type:actions.ADD,
        payload: {
          message:message,
          username:username,
          id:Date.now()
        }
       }),2000)
    }
}

let removeMessage = (id) => {
  return dispatch => {
    setTimeout(() => dispatch({
            type:actions.REMOVE,
            payload:id
        })
    ,2000)
  }
}

class App extends Component {

  constructor(props){
    super(props);
    this.state = {
      username:"",
      message:""
    }
  }

  onUsernameChange = (event) => {
   event.preventDefault();
   let username = event.target.value;
   this.setState(() => {
     return {
       username: username
     }
   })
  } 

  onMessageChange = (event) => {
    event.preventDefault();
    let message = event.target.value;
    this.setState(() => {
      return {
        message: message
      }
    })
  }

  
  onSubmit = (event) => {
    event.preventDefault();
    let message = this.state.message;
    let username = this.state.username;
    this.props.add(message,username);

  }

  render() {
    return (
      <div className="wrapper">
        <form name="post" onSubmit={this.onSubmit}>
        <input style={{padding:'10px',margin:'10px',width:'70%',textAlign:'left'}}
         type="text" 
         placeholder="username" 
         name="username" value={this.state.username}
         onChange={this.onUsernameChange} /><br />

        <textarea style={{padding:'10px',margin:'10px',width:'70%',rows:'50',cols:'40',textAlign:'left'}} 
        placeholder="message"
        name="message" 
        value={this.state.message}
        onChange={this.onMessageChange}>
         </textarea><br />

         <input style={{padding:'10px',margin:'10px',backgroundColor:'orange',textAlign:'left'}}
          type="submit"
           value="Post" />
        </form>
        <div>
          {this.props.messages.map((message) => {
            return <div style={{display:'inline-block',
            padding:'20px',cursor:'pointer', margin:'0 20px 20px 0',
             border:'1px solid black', width:'200px'}} key={message.id}
              onClick={() => this.props.remove(message.id)}>
              <p>Username: {message.username}</p>
              <p>Message: {message.message}</p>
            </div>
          })}
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    messages: state.messageList
  }
}

const mapDispatchToProps = dispatch => {
return {
 add : (username,message) => dispatch(addMessage(username,message)),
 remove: (id) => dispatch(removeMessage(id)) 
}
}

export default connect(mapStateToProps,mapDispatchToProps)(App);

 

The only changes made are with the action creators and mapDispatchToProps function.

#13 action creator now returns a function which accepts a dispatch function as a parameter. dispatch function is made available to it using redux-thunk.

#118 to #123 no more requires passing dispatch explicitly to action creators and dispatch is fired upon invocation of the function mapped as props to the component.

GitHub link:

https://github.com/krishna28/react-redux-async-thunk/tree/react-redux-with-redux-thunk

Thanks for reading.