Notes on React with Redux and Redux-Thunk: Part 1

The integration of Redux and Redux-Thunk has been a difficult experience for me in terms of better recall when building from scratch. I have read various documentation articles, blog posts and watched tutorials for the past two years and none stuck to me significantly to increase my confidence in handling this important feature in React application development.

Fortunately I recently finished Stephen Grider’s “Master React and Redux with React Router, Webpack, and Create-React-App. Includes Hooks!” UDemy course and his sections on both Redux and Redux-Thunk made so much sense to me that I wrote this notes article for a digested reference for future me!

Table of Contents

Redux and Redux-Thunk

The diagrams below are adapted from Grider’s presentation during the course.

The file structure context mentioned in the following sections will mirror the output of create-react-app

Redux Cycle

The integration of Redux to a React application starts with the store fed into a Provider that wraps over the root component.

In this store object the reducers and other middleware (such as the thunk) are set as arguments which serves as the main contact point for the other ingredients of this process.


Redux Cycle


The action creator (AC) defines the actual handling of whatever data in the state needs to be done. This has two kinds of returns: either a function or an object; this will be discussed later. Regardless, its final output will be an object with the following familiar structure:

const myAction = {
  type: "ACTION_TYPE",
  payload: "payload"
}

This is the action, a simple JS object with a type and an optional payload. Within the AC, there is also a dispatch() method provided to execute the action for the reducers to evaluate.

Actions can be executed in various ways

  1. Internally within the AC (through the thunk way):

     export const getVideos = (term: string) =>
       async (dispatch: TAppDispatch): Promise<void> => {
         const response: IYouTubeResults = await youtube.get('/search', {
           params: {
             q: term
           }
         })
    
         dispatch({
           type: EActionTypes.getVideos,
           payload: response.data.items
         })
       }  
    
  2. Directly provided when the AC is called inside a component:

    connect and mapDispatchToProps work with this pattern.

     interface IVideoItem {
       video: IVideo
       selectVideo: (video: IVideo) => { type: EActionTypes, payload: IVideo }
     }
    
     const VideoItem: React.FC<IVideoItem> = ({
       video,
       selectVideo
     }) => {
       const onClick = () => {
         selectVideo(video)
       }
    
       return (
         ...
       )
          
     export default connect(null, { selectVideo })(VideoItem)
    
  3. Hooks way with useDispatch

Finally, the reducers directly receive the actions and perform changes to the state depending on their content. This automatically connect to the store object and will reflect to listening child components in the app.

This is how to use the Redux Cycle to perform changes to the app’s overall state.

Middlewares in Redux

Grider provided the following characteristics and remarks on Redux middlewares:

  • a function that gets called with every action we dispatch.
  • has the ability to stop, modify or otherwise mess around with actions.
  • tons of open source middleware exist.
  • most popular use of middlewares is for dealing with asynchronous actions.
  • “Redux-Thunk” is a popular middleware for handling asynchronous issues.

Redux and middleware cycle

The inclusion of a middleware makes it easy to separate the responsibilities of different files in the project.


Redux-Thunk Middleware Cycle


The code below is a simplified, compact version of an AC that wraps over an asynchronous function that dispatches an action within itself.

export const getVideos = (term: string) =>
  async (dispatch: TAppDispatch): Promise<void> => {
    const response: IYouTubeResults = await youtube.get('/search', {
      params: {
        q: term
      }
    })

    dispatch({
      type: EActionTypes.getVideos,
      payload: response.data.items
    })
  }  

Redux-Thunk

This was an eye-opener for me because I wanted a way to organize the async operations in separate files. Before, I was just stuck in declaring them inside the component itself leading to a stiff application.

Grider shared the following differences and similarities of action creators with and without Redux-Thunk:

Default Rules Rules with Redux-Thunk
Return type ACs must return action objects ACs can return action objects
ACs can return functions
Action `type` property Actions must have a type property If an action object gets returned,
then it must have a type
Action `payload` property Actions can optionally have a payload

It mentioned that there are two kinds of ACs in the Redux-Thunk context. These will be discussed below.

Two kinds of action creators in Redux-Thunk

The default Redux action creator returns an action object but in the addition of Redux-Thunk, it can also return a function.

Nevertheless, either way the function must be evaluated to an action object once it is dispatched to the reducers.

The addition of function capabilities enables a powerful way to handle certain processes such as asynchronous network connections as seen in the getVideos() method defined earlier.


Redux-Thunk Cycle


References

Twitter, LinkedIn