Async React Basics with Redux-thunk & Redux-saga
Tags: react, redux, javascript, webdev
Published: 2018-10-08 13:43:22

---
title: Async React Basics with Redux-thunk & Redux-saga
published: true
description: React with redux and async Javascript
tags: react, redux, javascript, webdev
---

I have lots of free time lately so I decided to play around a bit with React & Redux. If you want to write maintainable asynchronous code using Redux, you need to pick a middleware like redux-thunk or redux-saga.

#What we're building
I love cats, so the functionality of the application is based on the [Cat API](https://thecatapi.com/). You can clone/fork the GitHub repo from [here](https://github.com/bnorbertJS/react_catapi).

The application looks something like this:
![catapi app](https://thepracticaldev.s3.amazonaws.com/i/64ocl0lsfupq43eqdsod.JPG)

If you click the "Fetch cats" button, it sends an HTTP GET request which returns a random cat image. If you click on "Fetch more cats" it returns an array of 5 random cats.
I know it's ugly and stuff but I don't really want to waste time with css. If you are interested in the full "project" and the css files as well, check out the github repo that I have already mentioned above.


The `fetchCats` function will be implemented using redux-thunk and `fetchMoreCats` will be written using redux-saga so that we can compare them.

#Getting started

```
create-react-app catapi_app
```
Let's install some dependencies first.
```
npm i --save react-redux redux redux-logger redux-saga redux-thunk
```
Next, we need to setup redux in `index.js`.

```javascript
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducers/index'

const loggerMiddleware = createLogger()

const store = createStore(
    rootReducer,
    applyMiddleware(
        thunkMiddleware,
        loggerMiddleware ))

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>, 
    document.getElementById('root')
);
```
This code will fail, because we do not have our `rootReducer`. So let's continue with that.

```javascript
// ./reducers/index.js

import { combineReducers } from 'redux'
import fetchCatReducer from './fetchCatReducer'

export default combineReducers({
    cats: fetchCatReducer
})
```
We have only one reducer so far, but I like to use combineReducer because if I need to add another one, it's much easier.

This code will still fail because now, we are missing the `fetchCatReducer`.

```javascript
// ./reducers/fetchCatReducer.js

const fetchCatReducer = (state = [], action) => {
    switch(action.type) {
        case "FETCH_CATS_SUCCESS":
            return [
                ...action.payload,
                ...state
            ]
        case "FETCH_CATS_START":
            return state
        case "FETCH_CATS_ERROR":
            return state
        default:
        return state
    }
}

export default fetchCatReducer
```
Whenever we dispatch an action, that action goes through `fetchCatReducer` and it updates our state accordingly.

* `"FETCH_CATS_SUCCESS"`: HTTP request was successful, we must update the state.
* `"FETCH_CATS_START"`: HTTP request has been started, this is the right place to for example display a busy indicator to the user. (Loading screen or something)
* `"FETCH_CATS_ERROR"`: HTTP request has failed. You can show an error component or something.

To keep the app simple, in case of `"FETCH_CATS_START"` or `"FETCH_CATS_ERROR"` I do nothing but returning the previous state.

#Redux-thunk

Currently, our app does nothing, because we need an action creator, to fire an action that our reducer handles.

```javascript
//./actions/fetchCats.js

/*Helper functions. remember, we have 3 action types so far,
 these functions return a plain object that has a 
type attribute that our reducer can handle.
in case of success request, 
the action has a payload property as well. 
That's the response cat from the server 
that we have requested*/

const fetchCatsError = () =>{
    return {type: "FETCH_CATS_ERROR"}
}

const fetchCatsStarted = () =>{
    return {type: "FETCH_CATS_START"}
}

const fetchCatsSuccess = (cat) => {
    return {type: "FETCH_CATS_SUCCESS", payload: cat}
}

// fetching a random cat starts now
const fetchCats = () => dispatch => {
    dispatch(fetchCatsStarted())

    fetch("https://api.thecatapi.com/v1/images/search",{
        headers: {
            "Content-Type": "application/json",
            "x-api-key": "YOUR_API_KEY"
        }
    })
    .then( catResponse => catResponse.json()) 
    .then( cat => dispatch(fetchCatsSuccess(cat)) )
    .catch( err => dispatch(fetchCatsError()))
}
```
Ye, in order to use this endpoint on CAT API, you need an api key.
`fetchCats` might look strange at first, its basically a function that returns another function that has a parameter `dispatch`. Once you call dispatch, the control flow will jump to your reducer to decide what to do. In our case, we only update our application state, if the request has been successful. Btw, that's why I have installed `redux-logger`. It constantly logs the changes of your state, and actions so its much easier to follow what's happening.

If you prefer the Async/await syntax, then you can implement the above function like this:
```javascript
const fetchCats =  () => async dispatch => {
    dispatch(fetchCatsStarted())
    try{
        const catResponse = await fetch("https://api.thecatapi.com/v1/images/search",{
            headers: {
                "Content-Type": "application/json",
                "x-api-key": "YOUR_API_KEY"
            }
        })
        
        const cat = await catResponse.json()
        dispatch(fetchCatsSuccess(cat))
    }catch(exc){
        dispatch(fetchCatsError())
    }
}
```
#App component
I don't want this post to be too long, so I skip the implementations of the components. I'll show you how the `App.js` looks like, if you are interested in the complete code, check it out on [GitHub](https://github.com/bnorbertJS/react_catapi).

```javascript

//./components/App.js

import React, { Component } from 'react'
import Button from './proxy/Button'
import CatList from './CatList'
import '../css/App.css'
import { connect } from 'react-redux'
import fetchCats from '../actions/fetchCats'

class App extends Component {
	render() {
		return (
			<div className="App">
				<Button className="primary" text="Fetch cats" onClick={this.props.fetchCats}/>
				<Button className="secondary" text="Fetch more cats"/>
				<header className="App-header">
					<CatList cats={this.props.cats}/>
				</header>
			</div>
		)
	}
}

const mapStateToProps = (state, ownProps) => ({
		cats: state.cats
})

export default connect(mapStateToProps, { fetchCats })(App);
```
#Redux-saga

Redux-saga is a redux middleware that allows us to easily implement asynchronous code with redux.

To initialize it, we need to adjust our `index.js` a bit.
```javascript
//./index.js
...
import createSagaMiddleware from 'redux-saga'
import watchFetchMoreCatsSaga from './saga/fetchMoreCats'

//init
const sagaMiddleware = createSagaMiddleware()

//run
sagaMiddleware.run(watchFetchMoreCatsSaga)
...
```
In the `saga` folder, create a new file called `fetchMoreCats`.

```javascript
//./saga/fetchMoreCats

import { takeLatest, put } from "redux-saga/effects";

//Every time we dispatch an action 
//that has a type property "FETCH_MORE_CATS"
// call the fetchMoreCatsSaga function
export default function* watchFetchMoreCatsSaga(){
    yield takeLatest("FETCH_MORE_CATS", fetchMoreCatsSaga)
}

//query 5 cat image at the same time
function* fetchMoreCatsSaga(){
    yield put({type: "FETCH_MORE_CATS_SAGA_START"})

   const catResponse = yield fetch("https://api.thecatapi.com/v1/images/search?limit=5",{
        headers: {
            "Content-Type": "application/json",
            "x-api-key": "YOUR_API_KEY"
        }
    })

    const cats = yield catResponse.json()

    yield put({type: "FETCH_MORE_CATS_SAGA_SUCCESS", payload: cats})
}
```
Those `function*` thingies are called generator functions. If you want to know more about them, click [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*).

The `takeLatest` function can be replaced by `takeEvery` for example, but one cool feature of `takelatest` is that it only takes the last "event". In our case, if we rapidly click the button like 100 times, then our app sends 100 requests pretty much DDOSing the API :D. So instead of disabling the button every time it's getting clicked, we can use `takeLatest`. 

As you can see, by calling the `put` function we can fire actions just like we did with `dispatch`. So let's adjust our `./reducers/fetchCatReducer.js` to handle our new saga actions.

```javascript
//./reducers/fetchCatReducer.js

...
case "FETCH_MORE_CATS_SAGA_SUCCESS":
            return [
                ...action.payload,
                ...state
            ]
        case "FETCH_MORE_CATS_SAGA_START":
            return state
        case "FETCH_MORE_CATS_SAGA_ERROR":
            return state
...
```
The `watchFetchMoreCatsSaga` generator function is constantly listening to the `"FETCH_MORE_CATS"` action and calls our `fetchMoreCatsSaga`. So in order to make this work, we need to first fire that action.

```javascript
//./actions/fetchMoreCats.js

const fetchMoreCats = () => dispatch =>{
    dispatch({type: "FETCH_MORE_CATS"})
}

export default fetchMoreCats
```
That's it. Every time we call `fetchMoreCats`, it dispatches `{type: "FETCH_MORE_CATS"}` which "invokes" our `watchFetchMoreCatsSaga` that calls `fetchMoreCatsSaga`.

So we need to import `fetchMoreCats` in our `App.js` and call it when the user clicks that button.

```javascript
//App.js

...
import fetchMoreCats from '../actions/fetchMoreCats'

//put this button in the render method
<Button className="secondary" text="Fetch more cats" onClick={this.props.fetchMoreCats}/>

//we need to map that function to the props of the App


export default connect(mapStateToProps, { fetchCats, fetchMoreCats })(App);
```
#The end

If you want to know more: [Saga documentation](https://redux-saga.js.org/)

If you have any questions, please let me know in the comment section or feel free to email me.