Note:- This is an attempt of mine to making JSX more easy, maintainable and fun to write.
Introduction
We all have been writing the conditional jsx in different manners whether its ternary hell, short-circuiting, or switch object to render specific component with key, or rendering the async list(array) with Arr.length > 0 && Arr.map something like that and so on.
And that was it, when it clicked to me and I started my journey to make writing of conditional jsx in a manner which would be easy, readable and maintainable to write.
But, first let's take a closer look at the problems which I am talking about.
Current Conditional JSX Problems
- Ternary becomes un-readable when
item ? true : falseclause are bigger than 5-10 lines. - Nested ternaries can create buggy, un-readable and hard to maintain code.
- Scattered logic due to
Object switching. Over the time it can grow larger. -
Managing two seperate type-definitions for Object switching and actual state.
type Status = "Active"|"Inactive"|"Banned" const status:Status = "Active" const StatusComp: Record<Status, ()=>ReactNode> = { "Active":()=><div>active</div>, "Inactive":()=><div>inactive</div>, "Banned":()=><div>Banned</div> } Un-avoidable
null/undefined,length checksoroptional chainingfor async list data.To render repeated/template jsx, writing
new Array(length).fill(0).map()code everytime.
Now let's look at the code snippets describing the conditional jsx that we write.
1. Ternary and short-circuting problem
function Dashboard() {
const [showUser, setShowUser] = useState(false);
const [currentUser, setCurrentUser] = useState(null);
return (
<div>
{/* Based on flag, show the content */}
{/* If no fallback needed */}
{showUser && <div>User modal component</div>}
{/* If fallback needed. */}
{/* This gets unreable if jsx is bigger than 5-10 lines */}
{showUser ? <div>User modal component</div> : <div>some other thing</div>}
</div>
);
}
2. Sync/Async list rendering problem
function List() {
const [notes, setNotes] = useState<{ id: number, title: string }[]>([])
useEffect(() => {
// assume, notes are coming from api
setNotes([
{ id: 1, title: 'some random' },
{ id: 2, title: "awesome" }
])
}, [])
return (
<ul>
{/* rendering notes */}
{/* manually checking the length and running map function. */}
{/* Which already look too repetitive and cumbersome to write often */}
{notes.length > 0 && notes.map((item) => {
return (
<li>{item.id} - {item.title}</li>
)
})}
</ul>
)
}
3. Switching object problem
function Switch(){
const userStatus: "Banned"|"Online"|"InActive" = "Online"
// This is often created to manage switch based logic.
// Which just creates two twisted sources for jsx.
// it always become un-managable by managing two seperate types
const StatusComponent = {
"Banned": ()=><div>banned status component</div>,
"Online": ()=><div>online status component</div>,
"InActive": ()=><div>in-active status component</div>,
}
const ActiveStatus = StatusComponent[userStatus]
return (
<div>
{/* Showing different status component based on the user status */}
{<ActiveStatus />}
</div>
)
}
I am pretty sure, you all have written these kind of code. Most of the time it's un-avoidable.
Whether it's ternary, short-circuit, object switching or async list rendering, there is so much un-readability, code become un-maintanble or buggy over the time if there are multiple conditions to satify.
I will not pretend that React is the best UI library out there. In fact most of these issues are aleardy solved in SolidJS, Svelte and VueJs. Let's take a look at them
SolidJS Conditionals
<Show when={loggedIn()} fallback={<button onClick={toggle}>Log in</button>} <button onClick={toggle}>Log out</button> </Show>
Svelte Conditionals
{#if count > 10} <p>{count} is greater than 10</p> {:else if count < 5} <p>{count} is less than 5</p> {:else} <p>{count} is between 5 and 10</p> {/if}
VueJS Conditionals
<h1 v-if="awesome">Vue is awesome!</h1> <h1 v-else>Oh no π’</h1>
Since, all of these frameworks are fine-grained unlike React, which is coarse-grained(components are re-rendered on state update) and how jsx is parsed, due these it becomes impossible to implement these kind of component based conditional rendering in JSX.
After lots of experimention, long before even I knew about these frameworks, I build these solutions for solving out the conditional jsx in react.
Solving The Problems
1. If-Else component -> Replacing Ternary and Short-circuit
We can tackle the short-circuit and ternaries with If-Else syntax, which feels like natural if-else code, easy to write, read and maintain.
Super clean!!!
- Handle only sync values.(async value support is in-progress)
- If-Then-Else syntax
import {If, Then, Else} from 'classic-react-components'
function Dashboard() {
const [showUser, setShowUser] = useState(false)
return (
<div>
{/* No ternary for simple toggling. Use if-else */}
<If condition={showUser}>
<Then>
<div>User modal component</div>
</Then>
</If>
{/* Use if-else syntax */}
<If condition={showUser}>
<Then>
<div>User modal component</div>
</Then>
<Else>
<div>some other thing</div>
</Else>
</If>
</div>
)
}
2. For component -> Solving sync/async list rendering
For rendering the sync/async list, we have For component, which just feels like normal for-loop. It takes data, which gets inferred in the callback automatically. No null/undefined checks etc.
Code already become minimal and super easy write and remember.
- Type-safe
- No null checks
function List() {
const [notes, setNotes] = useState<{ id: number, title: string, description?: string }[]>([])
useEffect(() => {
// assume, notes are coming from api
setNotes([{ id: 1, title: 'some random' }, { id: 2, title: "awesome" }])
}, [])
return (
<ul>
{/* rendering notes */}
{/* No undefine check or Array.map */}
{/* Fully type-safe */}
<For data={notes}>
{(item, index)=> <li key={index}>{item.id} - {item.title}</li>}
</For>
</ul>
)
}
3. Switch-Case component -> Solving switching of object
And lastly we have Switch-Case component, you heard it right. We can write switch-case code inside in our jsx finally!!!.
- Fully type-safe
- Handles async values
- Provides syntax similar to
switch-case, it has alsodefault case.
function Switch(){
const userStatus: "Banned"|"Online"|"InActive" = "Online"
return (
<div>
{/* No type needed, using infering */}
<Switch item={userStatus}>
{({ Case, Default }) => {
return (
<>
{/* value is type-safe */}
<Case value='Banned'>
<div>banned status component</div>
</Case>
<Case value='Online'>
<div>online status component</div>
</Case>
<Case value='InActive'>
<div>in-active status component</div>
</Case>
{/* put any where, ordering does not matter for <Default> */}
<Default>
<div>this is default case</div>
</Default>
</>
)
}}
</Switch>
</div>
)
}
You already can see how our jsx becomes more readable, maintainable and fun to write. You can write if-else, for and switch-case code inside our jsx. That is so cool!!!
That's it for now. On next part, I will talk in-detail about all of the components.
Top comments (2)
Honestly this is looks clean and easy to understand the code. Great work... very small but effective library you made.
I am very happy to hear that.