4.4 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	| title | 
|---|
| ADR: Use of conditionals in JSX (deprecation of `<ConditionallyRender />`) | 
Background
Using the && operator in React can lead to unexpected rendering behavior when dealing with certain falsy values. In our codebase, the <ConditionallyRender /> component has been used to render React elements based on a boolean condition. However, it has certain drawbacks, which is why we would like to replace it with the ternary operator.
Pitfalls of && operator
While most truthy and falsy values behave as expected with the && operator, certain falsy values can produce unintended outcomes:
{NaN && <p>❔</p>} // will render `NaN`
{0 && <p>❔</p>} // will render `0`
{arr?.length && <p>❔</p>} // can render `0`
These issues can cause bugs in components that conditionally render UI elements based on numeric values or other potentially falsy conditions. For this reason, we use a wrapper.
<ConditionallyRender
    condition={arr?.length}
    show={<p>❔</p>}
/>
What's wrong with <ConditionallyRender />
While this solves leaking render issues, it has some drawbacks.
Poor TypeScript support
import type { FC } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
const SubComponent: FC<{ text: string }> = ({ text }) => <>{text}</>;
export const Test: FC<{ maybeString?: string }> = ({ maybeString }) => (
    <ConditionallyRender
        condition={maybeString}
        // ❌ TS Error: Type 'string | undefined' is not assignable to type 'boolean'
        // You have to use `Boolean(maybeString)`
        show={<SubComponent text={maybeString} />}
        // ❌ TS Error: Type 'string | undefined' is not assignable to type 'string'
        // you have to use `maybeString!` here
    />
);
Obfuscation of code smells and code cruft
Nested ternaries are easier to spot than nested <ConditionallyRender /> elements.
<div>
    <ConditionallyRender
        condition={Boolean(a)} 
        show={(
            <ConditionallyRender
                condition={Boolean(b)}
                show={<p>This is bad</p>}
            />
        )}
        elseShow={'Should be refactored'}
    />
</div>
Options considered
To avoid these issues, safer alternatives to the && operator can be used:
Convert to boolean
We could try to explicitly convert the condition to a boolean, ensuring that only true or false determine the rendering.
{Boolean(NaN) && <p>❔</p>}  // Won't render anything
{!!0 && <p>❔</p>}           // Also safe
Unfortunately Biome (the linter we use) does not include rules to automatically enforce a safer usage of the && operator, as ESLint did.
Ternary Operator
The ternary operator is a more explicit and safer approach. This covers cases where we need to return null or undefined.
{NaN ? <p>👍</p> : null}  // Won't render anything
It also plays nicely with TypeScript.
export const Test: FC<{ maybeString?: string }> = ({ maybeString }) =>
    maybeString ? <SubComponent text={maybeString} /> : null;
This is what we will use from now onwards.
Consequences
Positive: The codebase will become more type-safe and easier to understand.
Negative: The <ConditionallyRender /> component is imported in nearly 400 files. Significant refactoring effort is required.
Performance: There was no measurable performance difference between code with and without this component. This was tested on production bundle, on the features search (table) and projects list pages.
Migration plan
- 
Mark <ConditionallyRender />as deprecated in the codebase with a clear JSDoc comment.
- 
Automated refactoring with AST (Abstract Syntax Tree): There already is a script developed that can convert files between ConditionallyRenderand ternary syntax. It uses jscodeshift, a library. It will be put infrontend/scripts/transform.js.
- 
Each change will have to be reviewed. The order of refactoring should be: - New features that are behind feature flags.
- Non-critical or not in demand pages, like new signals or feedback component.
- Less complex pages, for example in /src/component/admin.
- More complex and critical pages, like strategy editing.
- Utilities and components used in many places (/src/component/common).
 
- 
Once all instances of <ConditionallyRender />have been refactored, remove the component from the codebase.