A data fetching example

The example in the previous section should explain clearly how to set up a universal application in React.

It is pretty straightforward, and the main focus is on getting things done.

However, in a real-world application, we will likely want to load some data instead of a static React component, such as App in the example. Suppose we want to load Dan Abramov's gists on the server and return the list of items from the Express app we just created.

In the data fetching examples in Chapter 5, Proper Data Fetching, we looked at how we can use componentDidMount to fire the data loading. That wouldn't work on the server because components do not get mounted on the DOM and the life cycle hook never gets fired.

Using hooks that are executed earlier, such as componentWillMount, will not work either, because the data fetching operation is async while renderToString is not. For that reason, we have to find a way to load the data beforehand and pass it to the component as props.

Let's look at how we can take the application from the previous section and change it a bit to make it load the gists during the server-side rendering phase.

The first thing to do is to change app.js to accept a list of gists as prop, and loop through it in the render method to display their descriptions:

  import React from 'react';
import { array } from 'prop-types';

const App = ({ gists }) => (
<ul>
{gists.map(gist => (
<li key={gist.id}>{gist.description}</li>
))}
</ul>
);

App.propTypes = {
gists: array
};

export default App;

Applying the concept that we learned in the previous chapter, we define a stateless functional component, which receives gists as a prop and loops through the elements to render a list of items.

Now, we have to change the server to retrieve gists and pass them to the component.

To use the fetch API on the server side, we have to install a library called isomorphic-fetch, which implements the fetch standards. It can be used in Node.js and the browser:

  npm install isomorphic-fetch

We first import the library in server.js:

  import fetch from 'isomorphic-fetch';

The API call we want to make looks as follows:

  fetch('https://api.github.com/users/gaearon/gists') 
.then(response => response.json())
.then(gists => {

});

Here, the gists are available to be used inside the last then function. In our case, we want to pass them down to App.

So, we can change the / route as follows:

  app.get('/', (req, res) => { 
fetch('https://api.github.com/users/gaearon/gists')
.then(response => response.json())
.then(gists => {
const body = renderToString(<App gists={gists} />);
const html = template(body);

res.send(html);
});
});

Here, we first fetch gists and then we render App to a string, passing the property.

Once App is rendered, and we have its markup, we use the template we used in the previous section, and we return it to the browser.

Run the following command in the console and point the browser to http://localhost:3000. You should be able to see a server-side render list of gists:

  npm run build && npm start

To make sure that the list is rendered from the Express app you can navigate here:

view-source:http://localhost:3000/

You will see the markup and the descriptions of gists.

That is great, and it looks easy, but if we check the DevTools console, we can see the following error:

Cannot read property 'map' of undefined

The reason we see the error is that, on the client, we are rendering App again but without passing any gists to it.

This could sound counterintuitive in the beginning because we might think that React is smart enough to use the gists rendered within the server-side string on the client. But that is not what happens, so we have to find a way to make the gists available on the client side as well.

You may consider that you can execute the fetch again on the client. That would work, but it is not optimal because you would end up firing two HTTP calls, one on the Express server and one in the browser.

If we think about it, we already made the call on the server, and we have all the data we need. A typical solution to share data between the server and the client is dehydrating the data in the HTML markup and hydrating it back in the browser.

This seems like a complex concept, but it is not. We will look at how easy it is to implement now.

The first thing we must do is to inject the gists in the template after we fetched them on the client. To do this, we have to change the template a bit:

  export default (body, gists) => ` 
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">${body}</div>
<script>window.gists = ${JSON.stringify(gists)}</script>
<script src="/bundle.js"></script>
</body>
</html>
`;

The template function now accepts two parameters—the body of the app and the collection of gists.

The first one is inserted inside the app element, while the second is used to define a global gists variable attached to the window object so that we can use it in the client.

Inside the Express route (server.js), we just have to change the line where we generate the template passing the body, as follows:

  const html = template(body, gists);

Last, but not least, we have to use the gists attached to a window inside client.js, which is pretty easy:

  ReactDOM.hydrate( 
<App gists={window.gists} />,
document.getElementById('app')
);

Hydrate was introduced in React 16 and works similar to render on the client side, whether the HTML has server rendered markup or not. If there is no markup previously using SSR, then the hydrate method will fire a warning that you can silence it by using the new suppressHydrationWarning attribute. 

We read  gists directly, and we pass them to the App component that gets rendered on the client.

Now, run the following command again:

  npm run build && npm start

If we point the browser window to  http://localhost:3000, the error is gone, and if we inspect the App component using React DevTools, we can see how the client-side App component receives the collection of gists.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.59.187