punkbit
punkbit

Reputation: 7707

Best conventions to use async/await on a react redux app

I'd like to know in which different ways can we use async/await in a React Redux (with thunk) and if there are good conventions, what are they? I think it'll be nice to have:

My working solution at the moment follows:

// MOCK data
const MOCK_DATA = [1, 2, 3, 4, 5]

// Action
export function myAction (payload) {
  return async (dispatch) => {
    const getter = () => {
      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({serverResponse: MOCK_DATA})
        }, 1200)
      })
      return promise
    }

    try {
      return await getter()
    } catch (error) {
      console.log(error)
    }
  }
}

// Container
class Foobar extends Component {
  async doSomething () {
    const data = await this.props.myAction()
  }
  render () {
    return (
      <div>
        <button onClick={this.doSomething}>Do something!</button>
      </div>
    )
  }
}

Upvotes: 0

Views: 1035

Answers (1)

Brian
Brian

Reputation: 1036

The problem with "await" is you are Blocking the event loop and with Thunk you have to handle the Promises & dispatcher directly.

There is an alternative to Thunk that is more easy to use. redux-auto

from the documantasion

redux-auto fixed this asynchronous problem simply by allowing you to create an "action" function that returns a promise. To accompany your "default" function action logic.

  1. No need for other Redux async middleware. e.g. thunk, promise-middleware, saga
  2. Easily allows you to pass a promise into redux and have it managed for you
  3. Allows you to co-locate external service calls with where they will be transformed
  4. Naming the file "init.js" will call it once at app start. This is good for loading data from the server at start

The idea is to have each action in a specific file. co-locating the server call in the file with reducer functions for "pending", "fulfilled" and "rejected". This makes handling promises very easy.

You example would look like this:

// MOCK data
const MOCK_DATA = [1, 2, 3, 4, 5]

// data/serverThing.js
export default function (data, payload, stage, result) {

  switch(stage){
    case 'FULFILLED':
        return result.serverResponse;
    case 'REJECTED':
        const error = result;
        console.log(error)
    case 'PENDING':
    default :
      break;
  }
  return data;
}

export function action (payload) {
  return Promise.resolve({serverResponse: MOCK_DATA})
}

// Container

import React  from "react"
import actions from 'redux-auto'
import { connect } from 'react-redux'

class Foobar extends Component {

  const loading = (true === this.props.data.async.serverThing) ? "loading..." : "";

  render () {
    return (
      <div>
        <button onClick={()=>actions.data.serverThing()}>Do something!</button> { loading }
      </div>
    )
  }
}

const mapStateToProps = ( { data }) => {
  return { data }
};

export default connect( mapStateToProps )(Foobar);

It also automatically attaches a helper object(called "async") to the prototype of your state, allowing you to track in your UI, requested transitions.

Upvotes: 2

Related Questions