Alexandre Annic
Alexandre Annic

Reputation: 10738

Use async/await in React component

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.

Upvotes: 1

Views: 1204

Answers (1)

Nik
Nik

Reputation: 2272

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.

Upvotes: 3

Related Questions