React Tip: Making use of switch-case in JSX

When you need to conditionally render a component based on multiple conditions



Situation

Imagine that you want to render different components based on the fetchStatus prop. Its possible values are loading, success, and error.

function BlogContent({
  fetchStatus,
  data,
}: {
  fetchStatus: 'loading' | 'success' | 'error'
  data: string | null
}) {
  // Conditional rendering here
  // ...
}

First Attempt

Nested ternary! I see this approach being used in too many codebases. I think this approach should be avoided when there are more than 2 possible rendering branches, as it's not good for readability in my opinion.

function BlogContent({
  fetchStatus,
  data,
}: {
  fetchStatus: 'loading' | 'success' | 'error'
  data: string | null
}) {
  // Nested ternary. This should be avoided.
  return (
    <div>
      {fetchStatus === 'loading' ? (
        <span>Loading...</span>
      ) : fetchStatus === 'success' ? (
        <p>{data}</p>
      ) : (
        <span>There was an error</span>
      )}
    </div>
  )
}

A Better Approach: Extract the Condition from JSX

A better approach is to extract the condition from JSX and store the result in a variable, e.g. content in this case.

function BlogContent({
  fetchStatus,
  data,
}: {
  fetchStatus: 'loading' | 'success' | 'error'
  data: string | null
}) {
  // Extract the condition from JSX and store the result in "content"
  let content
  if (fetchStatus === 'loading') {
    content = <span>Loading...</span>
  } else if (fetchStatus === 'success') {
    content = <p>{data}</p>
  } else {
    content = <span>There was an error</span>
  }
  // Use "content" variable in JSX
  return <div>{content}</div>
}

This approach is good enough. However, what if I want to put the full if-else condition right inside the JSX? Since it allows me to easily scan the code from top to bottom without having to jump around the lines.

Making Use of switch-case Inside JSX

Because we can only put an expression inside JSX, we need a little trick to overcome this limitation: IIFE to the rescue!

function BlogContent({
  fetchStatus,
  data,
}: {
  fetchStatus: 'loading' | 'success' | 'error'
  data: string | null
}) {
  // Using IIFE to put the condition inside JSX
  return (
    <div>
      {(() => {
        switch (fetchStatus) {
          case 'loading':
            return <span>Loading...</span>
          case 'success':
            return <p>{data}</p>
          case 'error':
            return <span>There was an error</span>
          default:
            throw new Error('There is an unhandled case')
        }
      })()}
    </div>
  )
}

That's a little tip for today. Happy coding everyone!