How to Write Conditional Hooks in React 🤦🏽‍♂️

TLDR; You can't. But here's what you should do – well explained. Split the component or move the conditional to a custom hook or conditionally fire an effect callback function.

Some people believe that their lives are fully compartmentalised e.g. your life as a developer doesn't interfere with your day to day life as a husband, wife or any other interaction you have with the world outside computers.

That's not quite the case as proven by research.

In this article, I'll take advantage of the deep wealth of interactions you get from your day-to-day lives to explain how to write conditional hooks in React.

Brace yourself for what's to come.

Representing a Hook with a Day-to-day Object

What is a hook?

I'm not trying to get you interviewed, but really, what is a hook?

If you throw the syntax out the door, a hook is essentially a construct that either holds some value for a later time or wraps the execution of a set of instructions. It's either storing value or "doing something".

// store value 
const [state, setState] = useState("hi curious reader 🎯")
const ref = useRef("yay 💃")

// do something 
useEffect(() => {
   sendAMillionDollars.current()
}, [])
code block representing the essentiality of a hook 

If we take cues from the beautiful world around us, we may represent a hook with a ... wait for it ... a light bulb! 💡

A light bulb

The light bulb is capable of storing value (electrical energy), and doing something (lightening up space)

Now comes the interesting bit.

A light bulb storing energy and lightening up a space 

Understanding the Conditional Hooks Problem as in the Real World

The need to invoke a hook conditionally isn't far fetched. Here's an example.

Assume you just bought a house. I know, I want one too.

You move into this new house, however, there's a condition placed in the contract. You bought the house under the conditions that the light bulbs in all rooms have been installed and by default will always stay on. You're not allowed to tamper with them.

It's a bit of a weird condition, but unknown to the landlord, you need a special room in the house that should always remain dark. Perhaps you've got a pet that can only fall asleep with no bulbs in the room.

What do you do?

Let your imagination go. In this story, you're the hero. No one questions your ideas.

What do you do?

There are two clear solutions you could employ in the real world.

Solution 1: Demarcate the Room or Area

Okay, you happen to be one of the gifted ones. You're great with your hands and can build anything. Even if you're not, perhaps like me, you don't mind giving it a try.

You can completely demarcate a part of the room with quality wood lined with some black cloth so no light shines through to the demarcated area.

A demarcated room

You close your pet's eyes, place it in the demarcated area and say good night (or say a prayer, if you don't trust the carpentry you've just done)

Now, how does this translate to code?

Most times you need a conditional hook, you can mostly resolve that by "demarcating" the component where the hook is being invoked. By splitting the component and pulling out the conditional logic into the new component, you can resolve this.

Consider the contrived example below:

const MyAwesomeComponent = ({isLoggedIn}) => {
  if (isLoggedIn) {
     const user = useLoggedInUser()
     return <div>{user.name}</div>
  }
  
  return <div>You should log in to see this </div>  
}
An example of a wrong custom hook conditional invocation

This is a simple example, but it does justice to most use cases you may encounter.

In this example, the custom hook useLoggedInUser is being invoked conditionally, against the rules of hooks.

What do you do? Demarcate.

Cut out the conditionally rendered UI into another component and render the component conditionally. Remember, hooks can't be called within conditionals, but you can conditionally render a component.

Here's how:

const LoggedInUserName = () => {
   const user = useLoggedInUser()
   return <div>{user.name}</div>
}

const MyAwesomeComponent = ({isLoggedIn}) => {
  if (isLoggedIn) {
     return <LoggedInUserName />
  }
     
  return <div>You should log in to see this </div>
}

There you go! Demarcation to the rescue.

In most cases, this suffices. However, you may find yourself in a situation where it makes sense to colocate a certain logic group within the same component.

Is there another solution?

Well, yes. Let's revert to the real world for some ideas.

Solution 2: Use a Light Bulb Switch

Okay, carpentry aside, how about controlling the light bulb with a switch? Does that count as tampering or violate the house purchase contract?

Controlling a bulb with a switch

Light bulbs can be controlled with a switch. This represents a gateway to controlling the state of the bulb. Do you know what else works this way? Props and function parameters!

In many cases where you've got a custom hook that triggers a particular piece of functionality, you may be able to wrap the execution in a conditional or prevent its execution altogether.

Consider the example below:

const User = ({userId}) => {
  const [data] = useFetchUserData(userId)
  
  return <div>{JSON.stringify({data})}</div>
}
code example for a data fetching hook

In this example, you may not want to make the data fetch for user data if the userId isn't available at the time of execution.

So what do you do?

Within useFetchUserData, you may prevent the actual data fetch if userId isn't available e.g.:

const useFetchUserData = (userId) => {
   // conditionally do something or return early ... 
   if(!userId) {
      return 
   }
   // ... go-ahead to fetch data  
}

You may be saying to yourself, what if I don't have a custom hook ... well, make one, or conditionally perform an action within the hook.

Here's the previous example we had:

const MyAwesomeComponent = ({isLoggedIn}) => {
  if (isLoggedIn) {
     const user = useLoggedInUser()
     return <div>{user.name}</div>
  }
  
  return <div>You should log in to see this </div>  
}

You could go-ahead to do this:

const MyAwesomeComponent = ({isLoggedIn}) => {
  const user = useLoggedInUser(isLoggedIn) 
 
  return user ? <div>{user.name}</div> : 
   <div>You should log in to see this </div>  
}

This works alright, but you should avoid this sort of dependency if you don't need it. With the above example, creating a separate component is the better solution. Keep the useLoggedinUser hook simple and with a single responsibility.

You may also find yourself in a situation where you aren't working with a custom hook but need to conditional action some side effects. Well, thankfully, the useEffect hook can conditionally perform a side effect e.g.:

const MyAwesomeComponent = ({isLoggedIn}) => {
 useEffect({
     if(!isLoggedIn) {
       // do nothing 
       return 
     }
     
     // do something otherwise 
     
 }, [isLoggedIn])
}

The Cons of This Analogy

Analogies are great for blurring the boundaries between seemingly unrelated concepts and enforcing a deeper understanding of a concept. However, sometimes they aren't without faults.

In this analogy, solution 1 may seem like the more difficult solution, and yes, it's difficult to demarcate a room!

However, it's not nearly as difficult with components. If you have an unreasonably large component, then demarcation lends itself well anyway. Don't shy away from this. It makes things easier in the long run.

With solution 2, you can easily bend a custom hook to your will e.g. by passing it an additional parameter. Except this is important e.g. the custom hook already needs this parameter to execute the function it was built for, then use this sparingly or be sure it's the right solution for your use case.

Conclusion

Let's end this how it started. How do you write a conditional hook? TLDR; you mostly can't. However, demarcating your logic i.e splitting your logic into another component or moving the conditional invocation to hook in question e.g. a custom hook will solve the problem.

Hope you had a good read!

Ohans Emmanuel

Ohans Emmanuel