1

Is it a good practice to use async/await directly in React component then store the result in the store ? For example:

class User extends Component {

    render() {
        return <div>{this.props.user.name}</div>
    }

    componentWillMount() {
        this.getUser();
    }

    async getUser() {
        try {
            const user = await userAction.get();
            this.props.storeUser(user);
        } catch (err) {}
    }
}

const state2props = (state) => ({
    user: state.User.user
});

const dispatch2props = dispatch => ({
    storeUser: (user) => dispatch(userReducer.store(user)),
});

export default connect(state2props, dispatch2props)(User);

It seems more flexible than the classic react/redux pattern.

1 Answer 1

3

Yes, you can use async/await in react components. It's not a bad practice It's just architecture question.

There are lots of ways to implement async logic in applications. In small application you can implement async logic in react components. When your application grow up, you will get some issues like duplicated code (for example you want to fetch user in several react components), code composition and code splitting.

You can use redux-thunk https://github.com/gaearon/redux-thunk, redux-saga https://github.com/redux-saga/redux-saga, redux-logic https://github.com/jeffbski/redux-logic or any other solution.

Moreover you can create your own custom middleware such as:

const reactions = {};

export const addReactions = signals => {
  reactions = { ...reactions, ...signals };
};

export default (signalMiddleware = ({ getState, dispatch }) => next => action => {
  if (!action.signal) {
    return next(action);
  }

  if (!reactions[action.signal]) {
    throw new Error(`There is no handler for ${action.signal} signal`);
  }

  reactions[action.signal]({ getState, dispatch, payload: action.payload });
});

Such middleware allows you implement business logic into separate layer. For example:

import { addReactions } from './path/to/signalMiddleware';

// Describe your Actions for middleware:
const fetchUser = (id) => ({
    signal: 'FETCH_USER',
    payload: id
});

const anotherAction = () => ({
    signal: 'SOME_ANOTHER_ACTION_WITH_USER',
});


// Describe your business logic using middleware:

addReactions({
    FETCH_USER: async ({dispatch}, {id}) => {
        const user = await fetcher.get(id);
        dispatch({
            type: 'RECEIVE_USER',
            payload: user,
        });
    },
    SOME_ANOTHER_ACTION_WITH_USER: () => {
       // do some awesone job :)
    }
}) 

So our react component could be:

class User extends Component {

    render() {
        return <div>{this.props.user.name}</div>
    }

    componentDidMount() {
       this.props.dispatch(fetchUser(123));
    }   
}

export default connect(state2props, dispatch2props)(User);

Now you can divide your application architecture into 3 layer:

1) View — react-components

2) Business logic — your middleware

3) Data logic — your reducer

Between view and business layer we use specific actions with signal field and without type field. Between business and data logic we use actions with type field.

This architecture allows you to get a strict separation of layers. This architecture is useful in big applications.

In small application it's ok to use redux-thunk or write async logic in react-components.

Sign up to request clarification or add additional context in comments.

4 Comments

I understand this approach but what if I got 2 components consuming the user and in one of them I want to do something special once the user is fetched (for example change the window location) ? With this logic it seems tricky to do.
You can add such logic in componentWillReceiveProps. After user is received, componentWillReceiveProps will be called. Also you can move common code to separate function and create several actions with signal field. The main goal of described architecture is excluding duplicates.
It what I am doing but in my case this kind of special actions append often. It seems a bit hacky to me. Anyway thanks to your detailed answer !
I have an idea of it: If you have lots of specific situations, you can try to create a signal action with a promise: const fetchUser = (id) => ({signal: "FETCH_USER", payload: id, promise: new Promise}). Redux dispatch function returns a dispatched action. So you can handle promise in react component and paste you custom logic here. But you have to resolve promise directly in your middleware layout.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.