Search code examples
reactjsreduxreact-reduxredux-thunk

React server side rendering using data called from API


I use react-redux + thunk + redial and generate my HTML on server side. I want to generate meta tags using user data coming from another server by react-helmet. in my server:

import { trigger } from 'redial';
trigger('fetch', components, locals)
      .then(() => {
          const state = getState();
          var html = ReactDOMServer.renderToString (
              <Provider store={store} key="provider">
                  <ReactRouter.RouterContext {...renderProps} />
              </Provider>
          );
let head = Helmet.rewind();
response.send(`<!DOCTYPE html>
              <head>
                <meta charSet="UTF-8"></meta>
                <meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1"></meta>
                <meta name="viewport" content="width=device-width, initial-scale=1"></meta>
                <link rel='stylesheet' href='/css/bootstrap.css'/>
                ${style? "<style>"+style+"</style>" : "<link rel='stylesheet' href='/css/app.css'/>"}
                    ${head.title}
                    ${head.meta}
                    ${head.link}
                    ${head.script}
                </head>
                <body>
                  <div id="app">${html}</div>
                  <script src='/javascript/bundle.js'></script>
                     ${popUp}
                </body>
              </html>`);

In my component I call dispatch in render() function to run a function in actions file which calls an API and returns user data. No matter where I use dispatch, it has the same result. In the best condition React returns the request and waits for the data coming from the other server. But it returns nothing due to condition I set to check received data from server.

My component

    var profile=[], shouldRender = false
export var Profile = React.createClass({
render () {
  var {x,y,z} = this.props;
  var pathArray = (this.props.path).split( '/' );
  if(!canUseDOM && shouldRender == false)dispatch(
    actions.theProfile(x,y,z)
  ).then((es) => {profile = es; shouldRender= true;Promise.resolve()})
  .catch((et) => console.log("error"));

if(shouldRender){
  console.log("got inside");
  return (
    <div  className="container">
      <RightSideBar page="profile"/>
      <Helmet
            title={metaData('title', name, profileId)}/>
      <div className="col-md-6 col-xs-12 col-md-offset-3 content right">
        <div className="row">
          {profileContent(ideaTypes)}
        </div>
      </div>
      <div className="col-md-3 col-xs-12 sidebar-left hidden-xs hidden-sm hidden-md">
        <LeftSideBar page="profile"/>
      </div>
    </div>
  )
 }
});

and my action which calls API using axios

    export var viewingProfile = (ux,y,z) => {
    return function (dispatch, getState)  {
      return axios.post(root + '/api/getUser', {
          Id: x,
          orgid: y,
          profileId: z
      })
      .then(function (res) {
        if(res.data.statusEntity.statusCode == -201){
          window.location.href = "/user-not-found"
        } else {
          dispatch(updateState(res.data))
          return res.data;

        }
      })
    }
  }

Solution

  • Finally after a lot of work on rendering data coming from another server in react server-side rendering, I found the best solution. First, you don't need redial, that's redundant, at least in my case. So I removed it from my source. then, I moved

    dispatch(
     actions.theProfile(x,y,z)
    )
    

    to my routes

    export default (store) => {
     const requireApiCall= (nextState, replace, cb) => {
      var state = store.getState();
        return store.dispatch(actions.theProfile(x,y,z)).
        then(()=>{
          cb()
        })
    
     };
     <Route path="/" component={App}>
       <IndexRoute  component={Home}/>
       <Route path="profile" component={Profile } onEnter={requireApiCall}>
       </Route>
     </Route>
    }
    

    The dispatch will execute and the routes wait for call back "cb()" and when my API call is answered, my store gets update with received data. Then action resolves the promise and then the promise inside requireApiCall resolves and returns the call back "cb()". routes begin rendering components and the answer sent to the client will be generated using received data.

    I know it's a little complicated but works perfectly.