grazdev
grazdev

Reputation: 1272

How to dispatch multiple action creators (React + Redux + Server-side rendering)

I've been following a great course on how to build a server-side rendered app with React and Redux, but I'm now in a situation that the course doesn't cover and I can't figure out by myself.

Please consider the following component (it's pretty basic, except for the export part at the bottom):

class HomePage extends React.Component {

    componentDidMount() {       
        this.props.fetchHomePageData();
    }   

    handleLoadMoreClick() {
        this.props.fetchNextHomePagePosts();
    }   

    render() {

        const posts = this.props.posts.homepagePosts; 
        const featuredProject = this.props.posts.featuredProject; 
        const featuredNews = this.props.posts.featuredNews; 
        const banner = this.props.posts.banner; 
        const data = ( posts && featuredProject && featuredNews && banner ); 

        if( data == undefined ) {
            return <Loading />; 
        }

        return(
            <div>
                <FeaturedProject featuredProject={ featuredProject } />
                <FeaturedNews featuredNews={ featuredNews } />
                <Banner banner={ banner } />                
                <PostsList posts={ posts } heading="Recently on FotoRoom" hasSelect={ true } />
                <LoadMoreBtn onClick={ this.handleLoadMoreClick.bind( this ) } />               
            </div>
        ); 

    }

}

function mapStateToProps( { posts } ) {
    return { posts }
}

export default {
    component: connect( mapStateToProps, { fetchHomePageData, fetchNextHomePagePosts } )( HomePage ),
    loadData: ( { dispatch } ) => dispatch( fetchHomePageData() )
};

The above works fine: the loadData function makes an API request to fetch some data, which is fed into the component through the mapStateToProps function. But what if I wanted to fire multiple action creators in that same loadData function? The only thing that kind of works is if I write the function like this:

function loadData( store ) {
    store.dispatch( fetchFeaturedNews() );
    return store.dispatch( fetchHomePageData() );
}

export default {
    component: connect( mapStateToProps, { fetchHomePageData, fetchNextHomePagePosts } )( HomePage ),
    loadData: loadData
};

but this is not great because I need all data to be returned... Keep in mind that the exported Component ends up in the following route configuration:

const Routes = [
    {
        ...App, 
        routes: [
            {
                ...HomePage, // Here it is!
                path: '/', 
                exact: true
            },
            {
                ...LoginPage, 
                path: '/login'
            },              
            {
                ...SinglePostPage, 
                path: '/:slug'
            },
            {
                ...ArchivePage, 
                path: '/tag/:tag'
            },                                      
        ]
    }
];

and here's how the loadData function is used once the component is needed by a certain route:

app.get( '*', ( req, res ) => {

    const store = createStore( req ); 

    const fetchedAuthCookie = req.universalCookies.get( authCookie ); 

    const promises = matchRoutes( Routes, req.path ).map( ( { route } ) => {
        return route.loadData ? route.loadData( store, req.path, fetchedAuthCookie ) : null;
    }).map( promise => {
        if( promise ) {
            return new Promise( ( resolve, reject ) => {
                promise.then( resolve ).catch( resolve ); 
            }); 
        }
    });

    ...

}

Also, here's an example of the actions fired by the action creators. They all return promises:

export const fetchHomePageData = () => async ( dispatch, getState, api ) => {

    const posts = await api.get( allPostsEP );

    dispatch({
        type: 'FETCH_POSTS_LIST', 
        payload: posts
    });             

}

and the reducer:

export default ( state = {}, action ) => {
    switch( action.type ) {
        case 'FETCH_POSTS_LIST':
            return {
                ...state, 
                homepagePosts: action.payload.data 
            }                                       
        default: 
            return state; 
    }
}

Upvotes: 6

Views: 1541

Answers (1)

Tomasz Mularczyk
Tomasz Mularczyk

Reputation: 36179

So your actions return a Promise, and you are asking how can you return more than one Promise. Use Promise.all:

function loadData({ dispatch }) {
  return Promise.all([
    dispatch( fetchFeaturedNews() ),
    dispatch( fetchHomePageData() ),
  ]);
}

But... remember that Promise.all will resolve when all of it's Promises resolve, and it will return an Array of values:

function loadData({ dispatch }) {
  return Promise.all([
    dispatch( fetchFeaturedNews() ),
    dispatch( fetchHomePageData() ),
  ]).then(listOfResults => {
    console.log(Array.isArray(listOfResults)); // "true"
    console.log(listOfResults.length); // 2
  });
}

So you will probably want to handle it differently.

Upvotes: 2

Related Questions