erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
12k stars 2.5k forks source link

How to make parallel asyc calls #1262

Open govind999 opened 8 years ago

govind999 commented 8 years ago

Hi All,

Couple of questions.

  1. When the gadgets are loading first time, i am not seeing any event dispatch to load() function, may i know where is it called from or invoked?
  2. On page load i have to make 3 or 4 api calls, what is the common place to do that? that is not related to any component, but needed for the whole page.
  3. Instead of local, I want to make call to another api server, i think for this i can just the localhost:3003 to my api right?
VSuryaBhargava commented 8 years ago

@govind999 1) by gadgets if you mean Widgets then the load is being called in https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/Widgets/Widgets.js

@asyncConnect([{
  deferred: true,
  promise: ({store: {dispatch, getState}}) => {
    if (!isLoaded(getState())) {
      return dispatch(loadWidgets());
    }
  }
}])

2) The app component is loaded only once in this example ( its the navbar ) and it remains throughout the entire app so I think you can use that. In https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/App/App.js you can find the below code where multiple actions are being dispatched ( multiple rest calls can be made as well )

@asyncConnect([{
  promise: ({store: {dispatch, getState}}) => {
    const promises = [];

    if (!isInfoLoaded(getState())) {
      promises.push(dispatch(loadInfo()));
    }
    if (!isAuthLoaded(getState())) {
      promises.push(dispatch(loadAuth()));
    }

    return Promise.all(promises);
  }
}])

The third question it is not clear, There is an api server in this example which runs on port 3030. ( and you can modify that ). If you want to call a standalone api server you can just change the proxy settings in server.js and ApiClient.js or just proxy all requests from the api server.

govind999 commented 8 years ago

Thanks for the response. What i mean to ask is

instead of calling /api/ which is running on same server i can call any restful webservices right? for me that call should work both on client and server, i don't want the ajax calls on client going via node.

VSuryaBhargava commented 8 years ago

@govind999 A page which uses rest calls to show data will have to make that call either in the server or client.

If you open that page directly it will be called from server and not from client ( as the page is rendered on server side and the same data is carried forward to the client).

If you load a different page and then go to the page with the call, it will call the api through ajax to get the data to render the page.

If your app is running in http://xxx:3000 then only requests to http://xxx:3000 will normally be allowed by the browser. So you can't call some other domain's api without them allowing permission via adding the allow cross origin header so if you use this example the requests will have to be through node ( you can proxy them to match your needs though).

A workaround would be if your app server is behind another server like nginx you can write rules that allow a few requests to go to your app and the others to go to the api server.

oyeanuj commented 8 years ago

@VSuryaBhargava Can you achieve the second point you mentioned (in your first comment),from the action creator themselves?

So, something like this?

//in loadAuth function for example:
return {
  id: 1,    
  types: [
      AUTH_REQUESTED,
      AUTH_REQUEST_SUCCESS,
      AUTH_REQUEST_FAIL
    ],
  promise: (client) =>
    client.get(requestQuery())
    .then(() => loadInfo())
};      

This doesn't work for me but I was wondering if you've had any luck with that?

govind999 commented 8 years ago

@VSuryaBhargava

I will give it a try today,

most of API calls are CORS enabled. So there is no need to go via /api unless its required. We had one project in Fluxible which use fetcher and it calls /api/ and that inturn calls the restful api. I want to avoid the extra hop from browser which can has performance gain.

because client browser and node server are going to be at different places.

VSuryaBhargava commented 8 years ago

@oyeanuj

In your example I think the promise will resolve with the value that loadInfo is returning, so make sure that its returning some value that the reducers can use.

If you are trying to call multiple api's you can just create a promise like below

return {
  id: 1,    
  types: [
      AUTH_REQUESTED,
      AUTH_REQUEST_SUCCESS,
      AUTH_REQUEST_FAIL
    ],
  promise: (client) => {
    return new Promise((resolve, reject) => {
      client.get(requestQuery()).then((mainData) => {
        // conditions
        client.get(requestAnotherQuery()).then((additionalData) => {
          // conditions
          if (conditions) {
            resolve({mainData, additionalData});
          } else {
            reject({mainData, additionalData});
          }
        })
      })
    });
  }
};

Is there any reason to wait till the first api call has ended? if not you can just use Promise.all() to combine bith the api calls into a single promise.

I still think that dispatching multiple actions will be the best way to go ( unless the second call depends on the data from the first ).

oyeanuj commented 8 years ago

@VSuryaBhargava Thanks for the snippet, I'll try it out.

As for your other queries, I have to wait till the first api call has ended since the second call is conditional - if certain information is not fetched, fetch another set of information. And I've been trying to not dispatch multiple actions or chain them since then it becomes integrated with the component where the action is being dispatched. Ideally, for me, it would be the concern of action-creator to have conditional promises or API calls.

Essentially, to sum it up, I guess, I am looking for being able to set up conditional API calls within an action creator?

oyeanuj commented 8 years ago

@VSuryaBhargava The only problem with the above snippet is that I'd have to create that query in this action creator. Ideally, I'd like to dispatch a different action from the then of the API promise.

govind999 commented 8 years ago

@oyeanuj , my usecase is also exactly same, i need to call the parent call first and if that succeeds, grab data from there and call another api call, Can you document once it works for you?

Or is it all calls should be tied to components or is there anyway we can fire the root api call on some kind of load?

VSuryaBhargava commented 8 years ago

@oyeanuj

I got it to work in this way, I passed the dispatch function from @asyncConnect in src/containers/Widgets.js

@asyncConnect([{
  deferred: true,
  promise: ({store: {dispatch, getState}}) => {
    if (!isLoaded(getState())) {
      return dispatch(loadWidgets(dispatch));
    }
  }
}])

and in the src/redux/modules/widgets.js

I moved the load function below the save function, the load function now accepts dispatch as a parameter

export function load(dispatch) {
  return {
    types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
    promise: (client) => client.get('/widget/load/param1/param2').then((data) => {
      // condition
      if (data[0].owner !== 'John Connor') {
        const newData = {
          ...data[0],
          owner: 'John Connor'
        };
        dispatch(save(newData));
      }
      return data;
    })
  };
}
govind999 commented 8 years ago

@VSuryaBhargava thanks so much, i will take a look at that, by the way is this command working for you node ./node_modules/mocha/bin/mocha $(find api -name '*-test.js'), it throws error at $(find may be it is mac syntax

I got this working, it should be updated as below

"test-node": "node ./node_modules/mocha/bin/mocha ./api/__tests__/*.js --compilers js:babel-core/register",
VSuryaBhargava commented 8 years ago

@oyeanuj

Just came across this https://github.com/gaearon/redux-thunk

I think this is what you are looking for.