DEV Community

Ekaterina Anishkina
Ekaterina Anishkina

Posted on • Originally published at Medium

Fetching API Data with React Hooks, HOC or Render Prop

Let’s look at the implementation of data fetching using a new friend React Hooks along with good old friends: Render Prop and HOC (Higher Order Component). And find out if it’s true — ‘Old friends and old wine are the best’.

As life goes on changes, React changes for the better. In January 2019 React Hooks were added in React 16.8.0. Since then we can use local state and run side effects in functional components. Hooray! Nobody believed that it’s possible but everybody was dreaming about it. Before reading this article please make sure that you have read documentation about React Hooks.

React Hooks give an opportunity to stop using HOC and Render Prop in most of the cases. Since we have a lot of complaints about them:

HOC + Render Prop:

  1. “Wrapper hell” makes it complicated to read code and debug.

HOC:

  1. Problems with type annotation (flow, TypeScript).
  2. Sometimes it’s hard to determine an owner of component props.
  3. Props “naming collision”. You can override prop values with the same key.
  4. HOC composition is not always evident. E.g. you need “Authorisation HOC” wrapped component after “Profile Fetcher HOC”. It means “Authorisation HOC” should be executed first.

Render Prop:

  1. Render Prop usually doesn’t render any HTML elements, is used alongside JSX.

  2. Useless “git diff” due to line indenting for the whole JSX inside Render Prop. It looks you changed a lot, but actually just added only one Render Prop.

Let’s take a deeper dive and look at the example that demonstrates what is better — React Hooks or Render Prop. We will use Render Prop, its implementation is similar to HOC and less verbose. We will write a utility that fetches data from API. I’m pretty sure you’ve already coded this dozen times, so let’s see if we can write it better and more elegant.

We’ll use the popular library “axios”. With the simplest scenario we should handle the following statuses and actions:

  • fetching process (isFetching)
  • success response (responseData)
  • failed response (error)
  • cancel request and send the new one if the url is changed
  • cancel request for an unmounted component

1. Simple scenario

Let’s write initial state and a reducer function, which modifies state depending on fetched result: success or error.

Note: The term “Reducer” came from functional programming. A lot of JS developers learned it from Redux. It is a function that accepts the current state and an action and returns the new state.

We’ll use this function in both approaches.

We fetch data by calling axios.get() with url. Then we handle success and error statuses by dispatching an action to update the state. Don’t forget to cancel the request if the url has changed or the component has been unmounted. It’s simple, but we could write it differently. Let’s highlight the pros of these two approaches:

Hooks:

  1. Less code.
  2. Side-effect (data fetching) is easier to read, since it’s all in one place and not spread over the lifecycle methods.
  3. Request cancellation goes after request immediately.
  4. Simple code to trigger a side effect in response to prop changes.

Render Prop:

  1. It’s obvious in which lifecycle phase our code is executed.

It’s a fact that Hooks allow to write less code. So the effectiveness of you as a developer is growing. But you have to understand a new paradigm.

Everything is clear if you have lifecycle methods. We send initial request when a component is mounted (componentDidMount). Then refetch data and cancel the previous request if the props have changed (componentDidUpdate). And don’t forget to cancel the request if the component is unmounted (componentWillUnmount).

But now we perform all these side effects in the render. React team has taught us that it’s wrong. Hmm, it’s not in the render actually, but inside the useEffect hook, which will execute something asynchronously after each commit phase and DOM paint.

We don’t need to fetch data after every render, only after the initial one and whenever the url changes. That’s why we pass the url as the second argument in useEffect.

A new paradigm. To understand Hooks we need to learn new things. The most important one is the difference between the two phases: render and commit. In the render phase React figures out what changes should be applied to the DOM by comparing the results of the previous and the current render. In the commit phase React applies these changes to the DOM. During the commit phase componentDidMount and componentDidUpdate are called synchronously before the browser updates the screen. You might block the browser if you perform some large synchronous operations. Unlike these methods, the function passed to useEffect will be called asynchronously and will not block the browser from painting.

Another nice feature — useEffect will clean up after the previous effect and after a component is unmounted. Thanks to Rx that inspires the React team to take this approach.

Our utility usage is more convenient with Hooks.

Render Prop cons:

  1. It is not clear whether any markup is added or it’s just logic.
  2. If you want to use Render Prop state inside lifecycle methods you will have to create an extra class component.

Let’s add a new feature — trigger a new request by a user action. We will make a button that if clicked fetches a new avatar of your favourite developer.

2. Update data by user action

We will add a button that sends a request with a new username. The simplest solution is to store username in the component state and use it instead of passing it from props directly. . But in that case we will have to “copy-paste” this code for every component which need the same logic. To avoid “copy-pasting” let’s carry out this functionality into our utility.

We’ll use it in the following way:

Let’s code. Below are only the changes compared with the previous version.

If you look closely at the code you can notice:

  1. url is stored inside our utility;
  2. defaultUrl lets us identify that the url update is caused by the props. We need to keep track of the current props.url, otherwise a new request will not be sent;
  3. a new function ‘update’ has appeared. We return it to the component to send a new request by clicking the button.

With Render Prop we need getDerivedStateFromProps to update the local state in case of props.url changes. But with Hooks you can change the state in the render, there are no new abstractions. Finally we can modify the component state in the render, hooray!!!

Hooks require the only complication — the “update” function must be memoized so it’s not re-created on every render. In Render Prop “update” is simply a class method.

3. API polling

Now we are going to add another popular feature. Sometimes you constantly have to poll your API. For example, your favourite developer might have changed the avatar so you should be the first one to know. We need a polling parameter — interval.

Usage:

Implementation:

We created a new action type “poll” that increments “state.requestId” by one. If data is not being fetched we dispatch “poll” with setTimeout once the interval expires. Then we should send a new request when “state.requestId” changes.

With Hooks:

  1. perform setTimeout in a new useEffect,
  2. pass the new argument “requestId” in the old useEffect with data fetching.

With Render Prop:

  1. perform setTimeout in componentDidUpdate,
  2. compare previous to new “requestId” and “isFetching”,
  3. clear “timeoutId” in two places,
  4. add “timeoutId” class property.

With Hooks we can express the same things cleaner and shorter than we used to.

4. What’s next?

We can continue to expand the functionality of the utility: different configurations of query parameters, data caching, response and error handlers, data refresh with the same parameters — these are regular operations in any large web application. In our project we extracted all these features in a separate component. Yes, a component! It was Render Prop. Since Hooks have appeared we converted the component into a hook (useAxiosRequest) and we even found some bugs that we didn’t notice before! You can try it here.

Contributions are always welcome!
https://github.com/Turanchoks/use-axios-request

Discussion (0)