Conditional rendering in React made easier and reusable "no library"

If you ever worked with react it is likely that at some point you implemented some conditional rendering. such as the following

export const Component:FC=()=>{
   const [isLoading, setIsLoading] = useState<boolean>();
   //
   // Some logic
   //
   return (
     <Fragment>
      { isLoading? <LoadingComponent/> : <Content/>}
     </Fragment/>
   )
}

While this is fine.it is limited to two values(true & false) which is not always the case. moreover it complicates the component code as our component grows. lets see at other way of implementing this.

interface ConditionalRendererProps{
  activeState: string
  mapping: { [key: string]:React.ReactNode}
}

export const ConditionalRenderer:FC<ConditionalRendererProps>=(props)=>{
  return(
    <Fragment>
      { props.mapping[props.activeState] }
    </Fragment>
  )
}

The idea behind this implementation is to use ConditionalRenderer as a wrapper component and pass a mapping and activeState as a props.

mapping as its name indicates it contains state to component mapping. meaning what component corresponds to a given state.
activeState is selected state from the mapping props

While this could seem over complicating the implementation. it is actually more flexible that the first implementation and makes our code more cleaner. well how? okay to answer that lets take a look at another more common scenario where we need conditional rendering. when we have task that needs sometime to complete(e.g. when making API request). in this scenario there is more than two states for now to keep things simple lets agree we have four states namely initial, processing, complete & error & we want to render different components depending on the active state

export enum StateTypes{
  Init='INIT',
  Error='ERROR',
  Success='SUCCESS',
  Processing='PROCESSING',
}

The StateTypes enum defines all possible states, next lets define generic wrapper component for components containing asynchronous actions

interface StateMachineWrapperProps{
    asyncTask: Function
    component: React.ComponentType<any>
}
export const StateMachineWrapper:FC<StateMachineWrapperProps> =(props) =>{
  const machine = useAsync<string>(props.asyncTask)
  return (
    <ConditionalRenderer
        activeState={machine.currentState}
        mapping={{
          [StateTypes.Init]:<Fragment/>,
          [StateTypes.Processing]: <p>{machine.message}</p>,
          [StateTypes.Error]: <p>{machine.message}</p>,
          [StateTypes.Success]: <props.component {...machine.payload}/>
        }}
    />
  )
}

StateMachineWrapper renders components that comprises async actions.
It is highly likely that we have multiple components that communicate to an external API or perform some other async task and for every component we can use the StateMachineWrapper component and separate the side effect from our core component. lets see the usage...

function longRunningTask(){
      return new Promise((resolve, reject)=>{
          setTimeout(()=>{
              resolve({data: 'Async task completed'})
          },1000)
      });
}

To keep things simple the long running task does nothing practical but it is easy to modify the implementation according to your use case. finally lets take a look at the core component...

interface ContentProps{
  data: string
}
export const Content:FC<ContentProps>=(props)=>{
  return (
    <div>
      <h3>Content</h3>
      <p>{ props.data }</p>
    </div>
  )
}
export const ContentWrapper:FC =()=>{
  return (
      <StateMachineWrapper 
        asyncTask={longRunningTask}
        component={Content}/>
  )
}

the Content component is pure component and is decoupled with the side effect(or longRunningTask). as you can see the initial effort pays off finally because the components that mimic the StateMachineWrapper are pure components and the concerns are separated. this is one use case to demonstrate the ease of implementing conditional rendering in such a way.

Github gist can be found here

Thank you for reading, cheers!