ctrlplusb / react-universally

A starter kit for universal react applications.
MIT License
1.7k stars 244 forks source link

Universal Data Fetching #532

Closed gufranmirza closed 6 years ago

gufranmirza commented 6 years ago

How to fetch data in server side before rendering the page, This static method does't sees to work in a component.

static fetchData = () => {
  return fetch('http://localhost:8080/stories.json')
  .then((response => response.json()));
};
unleashit commented 6 years ago

Are you calling the method anywhere? I used to do my server rendering like that, and if you do you need to call it both on the server and then again in componentDidMount (which is invoked only on the client).

Check out how its done here: https://github.com/DominicTobias/universal-react/blob/master/app/Router.js. There's some pretty fancy code where an array of promises is dispatched based on the component/child components for the route. If you go to any of the components, you'll see the static method is called again in componentDidMount.

ctrlplusb (author of this repo) has react-jobs which is probably cleaner and offers some nice extras. You could wire it up yourself, or its included here in a couple different feature branches (feature/react-jobs and redux-opinionated if not others).

gufranmirza commented 6 years ago

I am using the #react-universally jobs branch https://github.com/ctrlplusb/react-universally/blob/feature/react-jobs/shared/components/DemoApp/AsyncJobsDemoRoute/Product/index.js

import React, { PropTypes } from 'react';
import { withJob } from 'react-jobs';
import { resolveAfter } from './utils';

function Product({}) {
  return (
    <pre>
      {jobResult}
    </pre>
  );
}

Product.propTypes = {
  // eslint-disable-next-line react/forbid-prop-types
  jobResult: PropTypes.object.isRequired,
};

export default withJob({
  work: (props) => {
    fetch("http://localhost:1337/api/public/v1/home/get_articles/?format=json")
    .then(response => {
      return response.json();
    })
    then((res) => {
      console.log(res);
      return res;
    })
  }
})(Product);

Whenever components renders data fetching is always performed on client side, I want to fetch data on server before rendering this component.

screenshot from 2017-11-08 19 43 34

unleashit commented 6 years ago

I think what you're saying is not that the fetch isn't running on the server, but that it's firing again on the client (and multiple times)? Does the response log to your terminal? If so, than it's working as expected on the server. As far as I know, React Jobs should trigger the fetch each time the component is created (both client/server) unless you pass withJob a 'defer' property.

As to why you're getting those client side requests, you need to somehow ensure the data is being passed and rehydrated on the client, and then check for it before you call the fetch. I use the redux branch, but it looks like the plain react-jobs includes async-bootstrapper, so that wiring should be built in I think. In your case, you could probably check the __JOBS_STATE__ global that react jobs/bootstrapper uses to rehydrate on the client. Something like:

export default withJob({
  work: (props) => {
    // find the correct property by logging __JOBS_STATE__
    // assuming here that the prop is an obj, not 100%
    // need to define isClient/isServer something like typeof window !== 'undefined' 
    if (isServer || (isClient && !Object.keys(__JOBS_STATE__.theCorrectProp).length)) {
      fetch("http://localhost:1337/api/public/v1/home/get_articles/?format=json")
        .then(response => {
          return response.json();
        })
        .then((res) => {
          console.log(res);
          return res;
        })
      } else return __JOBS_STATE.theCorrectProp;
  }
})(Product);

If you're using Redux, it's cleaner. You would probably use an action creator to fetch to the store, and then in your IF statement you could check the store (which would be rehydrated when the app starts) instead of the global var. But take my advice/above code for a grain of salt. It should work, but it may or may not be the cleanest way to do it sans Redux.

EDIT: after doing a little more reading of the react-jobs readme, it looks like the jobs state is being passed to the app as a prop (assuming the feature branch follows it). I would use react dev tools to figure out if it's being passed all the way to your component and if not, make sure it is. Then check the prop instead of the window object in your work function.

unleashit commented 6 years ago

p.s. you were missing the . before your last then, at least in the code you copy/pasted...