Progressive Enhancement Techniques for React Part 3
Data fetching is a difficult problem — especially when trying to have a consistent implementation on the server and the client. Ideally, each component would specify its own data fetching requirements. However, this can cause problems: when a component has its data, it may add more components, who in turn have data fetching requirements. In theory, this could go on indefinitely.
This is made more complicated by the fact that the server implementation of React Router needs the data before rendering the markup.
In this post, I’ll give a brief overview of what I use to fetch data. However, note that the techniques I’ll talk about are certainly not ideal, and I expect a lot more work to be done in this area.
For each route in React Router can contain a component. When you have a location, React Router will return an array of components that will be nested inside each other. This is important, as this is the only information you have before rendering the entire page. This also means that your data fetching requirements must be on each of the components directly referenced in the routes.
An example route component that fetches data can look like the following.
In the browser, this will fetch the data when mounted. If there are parameters provided to this, you may also need to fetch the data when the component is updated.
On the server, we’ll have to look through the component array, and see if the component defined the fetchData static method. We’ll then call all of the data fetching methods, and wait until they’re complete.
Data Fetching with Parameters
Sometimes you need to be more specific about the data you’re fetching. For example, when viewing the profile for an individual user, you need to fetch data only for that user. For this example, we’ll be fetching data based on a user query.
Implementing the filter as a form with method=‘GET’ will reload the page with query parameters. You’ll go from /cats to /cats?gender=female.
The data fetching in the component can use these query parameters to fetch the data accordingly. In React Router, you’ll have an object under location.query that contains the query parameters.
We’ll have to fetch data on mount and on update, as you’ll receive a new location property when the filter is set.
This could in theory get called multiple times — for example, when navigating away from the page and back. We don’t want to fetch data multiple times if we don’t have to.
The above will no-op when attempting to fetch data when the data is already available. Encapsulating this logic in the Redux action creators is likely one of many ways to determine whether you need to send a network request — but I have found this particularly helpful, since you can determine that from your state.
You may also find it easier to not use flags like didSetCats, and instead use the data directly. For example, you could set equally cats to null when you invalidate the data, and use that to determine whether you need to fetch data. In doing so, you’ll decrease the duplication in your state.
The last topic I’ll talk about is having getJson work in the browser, server, and test runner. How data is fetched is not part of the data, but it is part of the store. Redux middleware is built for scenarios like this. The middleware looks like the following.
Then add the middleware to your store like this,
And then in your actions, you’ll have to dispatch a call to fetchJson. Although be careful — if you don’t use dispatch, nothing will happen!
When we implemented the middleware in the store, it was for a browser. However, this approach is really versatile. On the server, you can use anything that uses the fetch api (like node-fetch). Additionally, you can also have it point to a different api endpoint, which is especially useful if the api is on a local server and you want to avoid a round-trip for each request. Significantly cleaner than polyfilling fetch, and it makes testing really easy!
Data fetching is hard.