Conditional React Hooks

One way to structure API calls in React is to use hooks. This gives you a clean separation of concerns for how data is fetched and creates a reusable component you can use for your API calls.

I wanted to implement a custom hook that calls an API but only when a user logs in. I have an AppComponent that has a prop called enableReviews. Should this value be true, it will call a custom hook that shows a movie and its reviews.

The code looks like this:

import {Movie} from "./Movie"

const AppComponent = (enableReviews) => {
  const {user} = useGetUserFlow();

  if(enableReviews){
    const {reviews} = useGetReviewsFlow(user);
    return <Movie reviews={reviews}/>
  } else {
    return <Movie/>
  }
}

However, I was left with this error:

React Hook "useGetReviewsFlow" is called conditionally. React Hooks must be called
in the exact same order in every component render. Did you accidentally
call a React Hook after an early return?

The reason for this error is depending on the condition, this potentially changes the order in which react-hooks are called. A hook must be called in the same order whenever a re-render happens, otherwise, there are unintentional side-effects that may occur. Therefore, React hooks can only be called at the top level of the component.

Now that we know this, how do we fix this code? There are a few different ways of fixing this.

Call and ignore

We can put the custom hooks at the top level regardless of what the prop is in this case.

import {Movie} from "./Movie"

const AppComponent = (enableReviews) => {
  const {user} = useGetUserFlow();
  const {reviews} = useGetReviewsFlow(user); // this was moved

  if(enableReviews){
    return <Movie reviews={reviews}/>
  } else {
    return <Movie/>
  }
}

This is simplest solution. However, this means if enableReviews is false, we're making essentially making an API call we're not using. If this app needs to be scaled for a lot of users, we'll be stressing the backend more than it needs to.

So this isn't an ideal solution.

A new component

A better solution instead is to write a separate component that is rendered only if enableReviews is set to true.

import {Movie} from "./Movie"

const AppComponent = (enableReviews) => {
  const {user} = useGetUserFlow();

  if(enableReviews){
    return <MovieWithReviews enableReviews={enableReviews} user={user}/>
  } else {
    return <Movie/>
  }
}

const MovieWithReviews= (enableReviews, user) => {
  const {reviews} = useGetReviewsFlow(user); // this was moved
  return <Movie reviews={reviews}
}

In this example, we move useGetReviewsFlow to a component that is only conditionally rendered if enableReviews is true. All the custom hooks still live at the top level of each React component, so React knows which order to re-render.

Hi 👋

I'm Vincent Tang, a web developer specializing in modern Javascript. This website is my digital garden of notes on backend, frontend, and devops! I also run a coding podcast called Code Chefs

TwitterGithub