DEV Community

loading...
Cover image for Conditional rendering in React made easier and reusable "no library"

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

bazenteklehaymanot profile image bazen-teklehaymanot ・2 min read

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/>
   )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}

Enter fullscreen mode Exit fullscreen mode

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',
}
Enter fullscreen mode Exit fullscreen mode

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}/>
        }}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

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)
      });
}
Enter fullscreen mode Exit fullscreen mode

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}/>
  )
}
Enter fullscreen mode Exit fullscreen mode

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!

Discussion (1)

Collapse
diasbruno profile image
Bruno Dias • Edited

Great article. This "pattern match"-like style is really great to keep things organized (and remove some `if's).

To avoid rendering unnecessary elements, you can add a function.


mapping={{
[StateTypes.Init]: (props) => <Fragment/>,
...
}}

Forem Open with the Forem app