
React Syntax Jsx
- 11 installs
- 6 repo stars
- Updated July 8, 2026
- openaec-foundation/react-claude-skill-package
Helps with frontend development tasks.
About
react-syntax-jsx is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.
- react-syntax-jsx
- Frontend Development
- AI-coding skill
React Syntax Jsx by the numbers
- 11 all-time installs (skills.sh)
- Ranked #1,658 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-syntax-jsxAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/react-claude-skill-package ↗ |
What it does
Helps with frontend development tasks.
Files
react-syntax-jsx
Quick Reference
JSX Compilation
JSX is syntactic sugar for React.createElement() calls. With the new JSX transform (React 17+, enabled by default in React 18/19), you do NOT need to import React for JSX to work:
// What you write:
<Button color="blue">Click me</Button>
// What the compiler produces (new transform):
import { jsx as _jsx } from 'react/jsx-runtime';
_jsx(Button, { color: 'blue', children: 'Click me' });NEVER import React solely for JSX in React 18/19 projects — the new JSX transform handles it automatically.
Three Rules of JSX
1. Return a single root element — use a wrapper <div> or Fragment <>...</> 2. Close ALL tags — including self-closing: <img />, <br />, <input /> 3. camelCase for attributes — className, strokeWidth, onClick, htmlFor
Exceptions: aria-* and data-* attributes keep their dashes (e.g., aria-label, data-testid).
Attribute Name Mapping
| HTML | JSX | Why |
|---|---|---|
class | className | class is a reserved word in JavaScript |
for | htmlFor | for is a reserved word in JavaScript |
tabindex | tabIndex | camelCase convention |
readonly | readOnly | camelCase convention |
maxlength | maxLength | camelCase convention |
aria-label | aria-label | Exception: kept as-is |
data-id | data-id | Exception: kept as-is |
Critical Warnings
NEVER use 0 && <Component /> — React renders the number 0 as visible text. ALWAYS convert the left side to a boolean:
// WRONG: Renders "0" on screen when count is 0
{messageCount && <Badge />}
// CORRECT: Boolean expression prevents rendering "0"
{messageCount > 0 && <Badge />}NEVER use array index as key for lists that can reorder, insert, or delete items — this causes state corruption. ALWAYS use stable unique identifiers:
// WRONG: Index keys break on reorder/insert/delete
{items.map((item, index) => <Item key={index} {...item} />)}
// CORRECT: Stable unique ID preserves component state
{items.map((item) => <Item key={item.id} {...item} />)}NEVER generate keys during render — Math.random() or crypto.randomUUID() inline creates new keys every render, destroying all component state.
NEVER use lowercase names for custom components — React treats lowercase tags as HTML elements. ALWAYS use PascalCase for component names.
---
Expressions in JSX
Use curly braces {} to embed JavaScript expressions inside JSX:
const name: string = 'Alice';
const imgUrl: string = getAvatarUrl(user);
return (
<div>
<h1>Hello, {name}</h1>
<img src={imgUrl} alt={`Avatar of ${name}`} />
<p>Result: {2 + 2}</p>
<p style={{ color: 'red', fontSize: 16 }}>Styled text</p>
</div>
);NEVER use statements inside {} — if, for, switch, let/const declarations are NOT expressions. Use ternaries, &&, or extract logic before the return.
String Literals vs Expressions
// String literal — use quotes
<input type="text" placeholder="Enter name" />
// Dynamic value — use braces
<input type="text" placeholder={dynamicPlaceholder} />
// NEVER mix quotes and braces on the same attribute
<input placeholder="{'wrong'}" /> // Renders the literal string "{'wrong'}"---
Conditional Rendering
| Pattern | When to Use |
|---|---|
if/else + early return | Completely different output branches |
Ternary ? : | Inline choice between two elements |
&& short-circuit | Show something or nothing |
| Variable assignment | Complex multi-step logic |
return null | Hide component entirely |
// Early return
function Greeting({ isLoggedIn }: { isLoggedIn: boolean }): JSX.Element {
if (!isLoggedIn) {
return <LoginPrompt />;
}
return <Dashboard />;
}
// Ternary
{isPacked ? <span>Packed</span> : <span>Pending</span>}
// Safe && (ALWAYS use boolean left side)
{items.length > 0 && <ItemList items={items} />}
// Variable assignment for complex logic
let content: JSX.Element;
if (isLoading) {
content = <Spinner />;
} else if (error) {
content = <ErrorMessage error={error} />;
} else {
content = <DataTable data={data} />;
}
return <div>{content}</div>;---
Lists and Keys
ALWAYS provide a key prop to the outermost element returned inside map():
interface Task {
id: string;
title: string;
completed: boolean;
}
function TaskList({ tasks }: { tasks: Task[] }): JSX.Element {
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.title} {task.completed && '(done)'}
</li>
))}
</ul>
);
}Key Rules
1. Keys MUST be unique among siblings (not globally) 2. Keys MUST NOT change between renders 3. Keys are NOT passed as a prop to the component — use a different prop name if needed 4. ALWAYS prefer database IDs or pre-generated stable IDs 5. Index as key is ONLY acceptable for static lists that never reorder
---
Fragments
Use Fragments to group elements without adding extra DOM nodes:
// Short syntax (cannot take props)
<>
<Header />
<Main />
<Footer />
</>
// Named Fragment (required when using key)
import { Fragment } from 'react';
{sections.map((section) => (
<Fragment key={section.id}>
<h2>{section.title}</h2>
<p>{section.content}</p>
</Fragment>
))}ALWAYS use <Fragment key={...}> (named import) when you need keys on fragments — the short syntax <> does NOT support the key prop.
---
TypeScript Component Typing
Function Component Signatures
// Preferred: Direct annotation on props parameter
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary';
onClick: () => void;
children?: React.ReactNode;
}
function Button({ label, variant = 'primary', onClick, children }: ButtonProps): JSX.Element {
return (
<button className={variant} onClick={onClick}>
{label}
{children}
</button>
);
}NEVER use React.FC<P> in new code — it previously included an implicit children prop (fixed in React 18 types) and hinders generic components. Direct annotation is clearer and more flexible.
PropsWithChildren
import type { PropsWithChildren } from 'react';
interface CardProps {
title: string;
}
function Card({ title, children }: PropsWithChildren<CardProps>): JSX.Element {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}Generic Components
interface SelectProps<T> {
items: T[];
selected: T;
getLabel: (item: T) => string;
onChange: (item: T) => void;
}
function Select<T>({ items, selected, getLabel, onChange }: SelectProps<T>): JSX.Element {
return (
<ul>
{items.map((item, i) => (
<li key={i} onClick={() => onChange(item)}>
{getLabel(item)} {item === selected && '(selected)'}
</li>
))}
</ul>
);
}
// Usage with type inference
<Select
items={users}
selected={currentUser}
getLabel={(u) => u.name}
onChange={setCurrentUser}
/>---
JSX Spread Attributes
Forwarding All Props
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
}
function LabeledInput({ label, ...inputProps }: InputProps): JSX.Element {
return (
<label>
{label}
<input {...inputProps} />
</label>
);
}
// Usage: all standard input attributes pass through
<LabeledInput label="Email" type="email" required placeholder="you@example.com" />Override Order
Props spread FIRST can be overridden by explicit props that follow:
// className from defaults is overridden by the explicit className
<input {...defaults} className="custom" />ALWAYS spread generic props first, then place specific overrides after — this ensures explicit props take precedence.
---
Boolean Attributes
// These are equivalent:
<input disabled />
<input disabled={true} />
// To NOT disable:
<input disabled={false} />
// NEVER omit the value when you want false — omitting means true---
Reference Links
- references/examples.md — Complete JSX patterns and code examples
- references/anti-patterns.md — Common JSX mistakes with explanations
Official Sources
- https://react.dev/learn/writing-markup-with-jsx
- https://react.dev/learn/javascript-in-jsx-with-curly-braces
- https://react.dev/learn/conditional-rendering
- https://react.dev/learn/rendering-lists
- https://react.dev/learn/passing-props-to-a-component
JSX Anti-Patterns
Common JSX mistakes with explanations and correct alternatives.
---
AP-1: The 0 && Trap
Severity: HIGH — renders visible "0" text on screen
// WRONG: When messageCount is 0, renders "0" as text
function Inbox({ messageCount }: { messageCount: number }): JSX.Element {
return (
<div>
{messageCount && <span>{messageCount} new messages</span>}
</div>
);
}
// CORRECT: Explicitly convert to boolean
function Inbox({ messageCount }: { messageCount: number }): JSX.Element {
return (
<div>
{messageCount > 0 && <span>{messageCount} new messages</span>}
</div>
);
}
// ALSO CORRECT: Use ternary
{messageCount ? <span>{messageCount} new messages</span> : null}
// ALSO CORRECT: Double negation
{!!messageCount && <span>{messageCount} new messages</span>}WHY: JavaScript's && returns the first falsy value. 0 is falsy but React renders it as text. false, null, and undefined render as nothing. The same trap applies to "" (empty string) — it renders as an empty text node.
---
AP-2: Array Index as Key for Dynamic Lists
Severity: HIGH — causes state corruption on reorder, insert, or delete
interface Todo {
id: string;
text: string;
}
// WRONG: Index key causes input state to mix up on reorder
function TodoList({ todos }: { todos: Todo[] }): JSX.Element {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>
<input defaultValue={todo.text} />
</li>
))}
</ul>
);
}
// CORRECT: Stable unique key preserves component identity
function TodoList({ todos }: { todos: Todo[] }): JSX.Element {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input defaultValue={todo.text} />
</li>
))}
</ul>
);
}WHY: When you delete item at index 2, the item that was at index 3 now has key "2". React thinks this is the same component and reuses its DOM and state. The user sees the wrong input values in the wrong rows.
ONLY acceptable for index keys: Static, read-only lists that NEVER reorder, insert, or delete items.
---
AP-3: Generating Keys During Render
Severity: HIGH — destroys all component state every render
// WRONG: New key every render = new component instance every render
{items.map((item) => (
<Item key={Math.random()} data={item} />
))}
// WRONG: Same problem with crypto
{items.map((item) => (
<Item key={crypto.randomUUID()} data={item} />
))}
// CORRECT: Use a stable identifier from the data
{items.map((item) => (
<Item key={item.id} data={item} />
))}WHY: React uses keys to match components between renders. A new key means React destroys the old component and mounts a new one. This causes: lost input focus, lost form state, lost scroll position, unnecessary DOM operations, and broken animations.
---
AP-4: Lowercase Custom Component Names
Severity: MEDIUM — component renders as unknown HTML element
// WRONG: Lowercase treated as HTML element
function myCard({ title }: { title: string }): JSX.Element {
return <div>{title}</div>;
}
// <myCard title="Test" /> renders as <mycard title="Test"></mycard> in the DOM
// CORRECT: PascalCase for custom components
function MyCard({ title }: { title: string }): JSX.Element {
return <div>{title}</div>;
}
// <MyCard title="Test" /> correctly invokes the componentWHY: JSX compilation uses the case of the tag name to decide whether to create an HTML element (lowercase) or call a component function (PascalCase).
---
AP-5: Using class Instead of className
Severity: LOW — React warns and auto-corrects, but code is technically wrong
// WRONG: class is a reserved JavaScript keyword
<div class="container">Content</div>
// CORRECT: Use className in JSX
<div className="container">Content</div>
// WRONG: for is a reserved JavaScript keyword
<label for="email">Email</label>
// CORRECT: Use htmlFor in JSX
<label htmlFor="email">Email</label>---
AP-6: Statements Inside JSX Expressions
Severity: HIGH — syntax error, code does not compile
// WRONG: if is a statement, not an expression
<div>
{if (isLoggedIn) { return <Dashboard />; }}
</div>
// CORRECT: Use ternary (expression)
<div>
{isLoggedIn ? <Dashboard /> : <LoginPrompt />}
</div>
// WRONG: for loop is a statement
<ul>
{for (let i = 0; i < items.length; i++) { <li>{items[i]}</li> }}
</ul>
// CORRECT: Use map (expression)
<ul>
{items.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>WHY: JSX curly braces accept JavaScript expressions (code that produces a value). Statements (if, for, while, switch, declarations) do NOT produce values and cause syntax errors.
---
AP-7: Missing Key on Mapped Elements
Severity: MEDIUM — React warns, performance degrades, subtle state bugs possible
// WRONG: No key prop
<ul>
{items.map((item) => (
<li>{item.name}</li>
))}
</ul>
// CORRECT: Key on the outermost returned element
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
// WRONG: Key on an inner element instead of the outermost
{items.map((item) => (
<div>
<span key={item.id}>{item.name}</span>
</div>
))}
// CORRECT: Key on the outermost element
{items.map((item) => (
<div key={item.id}>
<span>{item.name}</span>
</div>
))}---
AP-8: Using Short Fragment When Key Is Needed
Severity: MEDIUM — syntax error or missing key warning
// WRONG: Short fragment syntax cannot take key
{items.map((item) => (
<key={item.id}> {/* Syntax error */}
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</>
))}
// CORRECT: Use named Fragment import for keys
import { Fragment } from 'react';
{items.map((item) => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</Fragment>
))}---
AP-9: Spread Props Without Type Safety
Severity: MEDIUM — passes unexpected or dangerous props to DOM elements
// WRONG: Spreads all props including custom ones onto a DOM element
function Card(props: Record<string, unknown>): JSX.Element {
return <div {...props}>{props.children}</div>;
// If props contains { onCustomEvent: fn }, React warns about unknown DOM prop
}
// CORRECT: Destructure known props, spread the rest with proper typing
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
title: string;
variant?: 'default' | 'outlined';
}
function Card({ title, variant, children, ...divProps }: CardProps): JSX.Element {
return (
<div {...divProps} className={`card card--${variant ?? 'default'}`}>
<h3>{title}</h3>
{children}
</div>
);
}WHY: Spreading unfiltered props onto DOM elements passes unrecognized attributes to the DOM, causing React warnings and potentially invalid HTML.
---
AP-10: Mutating Props in JSX
Severity: HIGH — violates React's data flow, causes unpredictable behavior
// WRONG: Mutating a prop array before rendering
function ItemList({ items }: { items: Item[] }): JSX.Element {
items.sort((a, b) => a.name.localeCompare(b.name)); // Mutates parent's array!
return (
<ul>
{items.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
// CORRECT: Create a sorted copy
function ItemList({ items }: { items: Item[] }): JSX.Element {
const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name));
return (
<ul>
{sorted.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>
);
}WHY: Props are owned by the parent component. Mutating them changes the parent's data without the parent knowing, breaking unidirectional data flow and causing bugs that are extremely hard to trace.
---
Decision Tree: Is My JSX Pattern Safe?
| Check | If Yes | If No |
|---|---|---|
Left side of && can be 0 or "" ? | Convert to boolean (> 0, !!, Boolean()) | Safe to use && |
| List items can reorder/insert/delete? | NEVER use index as key | Index key is acceptable |
| Need key on a Fragment? | Use <Fragment key={}> | Use short syntax <> |
| Passing props to a DOM element? | Destructure and spread typed rest | Spread is safe for components |
| Sorting/filtering an array prop? | Create a copy first ([...arr]) | Direct use is fine for read-only |
| Custom component name? | MUST be PascalCase | Lowercase for HTML elements only |
JSX Patterns and Code Examples
Expressions in JSX
Embedding Variables and Computations
interface UserBadgeProps {
user: { name: string; level: number };
}
function UserBadge({ user }: UserBadgeProps): JSX.Element {
const greeting = `Welcome, ${user.name}`;
const isVIP = user.level >= 10;
return (
<div className="badge">
<h2>{greeting}</h2>
<span>Level: {user.level}</span>
<span>Status: {isVIP ? 'VIP' : 'Standard'}</span>
<span>Points needed: {(10 - user.level) * 100}</span>
</div>
);
}Inline Styles (Object Expression)
function ProgressBar({ percent }: { percent: number }): JSX.Element {
return (
<div
style={{
width: `${percent}%`,
height: 8,
backgroundColor: percent === 100 ? 'green' : 'blue',
borderRadius: 4,
transition: 'width 0.3s ease',
}}
/>
);
}Dynamic Class Names
interface TabProps {
label: string;
isActive: boolean;
onClick: () => void;
}
function Tab({ label, isActive, onClick }: TabProps): JSX.Element {
return (
<button
className={`tab ${isActive ? 'tab--active' : ''}`}
onClick={onClick}
aria-selected={isActive}
>
{label}
</button>
);
}---
Conditional Rendering Patterns
Early Return for Authorization
interface ProtectedRouteProps {
isAuthenticated: boolean;
children: React.ReactNode;
}
function ProtectedRoute({ isAuthenticated, children }: ProtectedRouteProps): JSX.Element {
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return <>{children}</>;
}Ternary for Toggle States
function ExpandableSection({ title, content }: { title: string; content: string }): JSX.Element {
const [isExpanded, setIsExpanded] = useState<boolean>(false);
return (
<section>
<button onClick={() => setIsExpanded(!isExpanded)}>
{title} {isExpanded ? '(collapse)' : '(expand)'}
</button>
{isExpanded ? <p>{content}</p> : null}
</section>
);
}Safe && Pattern (Boolean Left Side)
interface NotificationBadgeProps {
count: number;
}
function NotificationBadge({ count }: NotificationBadgeProps): JSX.Element {
return (
<div className="icon">
<BellIcon />
{count > 0 && <span className="badge">{count}</span>}
</div>
);
}Variable Assignment for Multi-Branch Logic
type Status = 'loading' | 'error' | 'empty' | 'success';
interface DataViewProps {
status: Status;
data: string[];
error: Error | null;
}
function DataView({ status, data, error }: DataViewProps): JSX.Element {
let content: JSX.Element;
switch (status) {
case 'loading':
content = <Spinner />;
break;
case 'error':
content = <ErrorMessage message={error?.message ?? 'Unknown error'} />;
break;
case 'empty':
content = <EmptyState message="No results found" />;
break;
case 'success':
content = (
<ul>
{data.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
);
break;
}
return <div className="data-view">{content}</div>;
}IIFE Pattern for Inline Complex Logic
function StatusMessage({ code }: { code: number }): JSX.Element {
return (
<div>
{(() => {
if (code >= 500) return <span className="error">Server Error</span>;
if (code >= 400) return <span className="warning">Client Error</span>;
if (code >= 200) return <span className="success">OK</span>;
return <span>Unknown</span>;
})()}
</div>
);
}---
Lists and Keys
Basic List Rendering
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
}
function ProductList({ products }: { products: Product[] }): JSX.Element {
return (
<div className="product-grid">
{products.map((product) => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
{product.inStock ? (
<button>Add to Cart</button>
) : (
<span className="out-of-stock">Out of Stock</span>
)}
</div>
))}
</div>
);
}Filtered and Transformed Lists
interface Person {
id: number;
name: string;
profession: string;
}
function ScientistList({ people }: { people: Person[] }): JSX.Element {
const scientists = people.filter((p) => p.profession === 'scientist');
return (
<ul>
{scientists.map((person) => (
<li key={person.id}>
{person.name}
</li>
))}
</ul>
);
}Nested Lists with Stable Keys
interface Category {
id: string;
name: string;
items: { id: string; label: string }[];
}
function CategoryList({ categories }: { categories: Category[] }): JSX.Element {
return (
<div>
{categories.map((category) => (
<section key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map((item) => (
<li key={item.id}>{item.label}</li>
))}
</ul>
</section>
))}
</div>
);
}Fragment with Key in Lists
import { Fragment } from 'react';
interface GlossaryEntry {
id: string;
term: string;
definition: string;
}
function Glossary({ entries }: { entries: GlossaryEntry[] }): JSX.Element {
return (
<dl>
{entries.map((entry) => (
<Fragment key={entry.id}>
<dt>{entry.term}</dt>
<dd>{entry.definition}</dd>
</Fragment>
))}
</dl>
);
}---
TypeScript Component Patterns
Props with Discriminated Unions
type AlertProps =
| { variant: 'success'; message: string }
| { variant: 'error'; message: string; retryAction: () => void }
| { variant: 'loading' };
function Alert(props: AlertProps): JSX.Element {
switch (props.variant) {
case 'success':
return <div className="alert-success">{props.message}</div>;
case 'error':
return (
<div className="alert-error">
{props.message}
<button onClick={props.retryAction}>Retry</button>
</div>
);
case 'loading':
return <div className="alert-loading"><Spinner /></div>;
}
}Generic List Component
interface ListProps<T> {
items: T[];
renderItem: (item: T) => JSX.Element;
keyExtractor: (item: T) => string;
emptyMessage?: string;
}
function List<T>({ items, renderItem, keyExtractor, emptyMessage = 'No items' }: ListProps<T>): JSX.Element {
if (items.length === 0) {
return <p>{emptyMessage}</p>;
}
return (
<ul>
{items.map((item) => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage
<List
items={users}
keyExtractor={(u) => u.id}
renderItem={(u) => <span>{u.name} ({u.email})</span>}
emptyMessage="No users found"
/>Polymorphic Component with as Prop
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<E>, 'as' | 'children'>;
function Box<E extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>): JSX.Element {
const Component = as || 'div';
return <Component {...props}>{children}</Component>;
}
// Usage
<Box>Default div</Box>
<Box as="section" id="main">Section element</Box>
<Box as="a" href="/about">Link element</Box>---
JSX Spread Attributes
Rest/Spread for Wrapper Components
interface TextFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
function TextField({ label, error, ...inputProps }: TextFieldProps): JSX.Element {
return (
<div className="field">
<label>
{label}
<input {...inputProps} aria-invalid={!!error} />
</label>
{error && <span className="field-error" role="alert">{error}</span>}
</div>
);
}
// All native input attributes pass through
<TextField
label="Username"
type="text"
required
minLength={3}
maxLength={20}
error={errors.username}
/>Composing Props from Multiple Sources
interface BaseButtonProps {
variant: 'primary' | 'secondary';
size: 'sm' | 'md' | 'lg';
}
const sizeClasses: Record<BaseButtonProps['size'], string> = {
sm: 'btn-sm',
md: 'btn-md',
lg: 'btn-lg',
};
function Button({
variant,
size,
className,
...rest
}: BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>): JSX.Element {
return (
<button
className={`btn btn-${variant} ${sizeClasses[size]} ${className ?? ''}`}
{...rest}
/>
);
}---
Boolean Attributes and Self-Closing Tags
function FormExample(): JSX.Element {
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
return (
<form>
{/* Boolean attribute: presence = true, omission = false */}
<input type="text" required readOnly={false} />
{/* Self-closing tags (no children) */}
<input type="email" />
<br />
<hr />
<img src="/logo.png" alt="Logo" />
{/* Dynamic boolean */}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}---
PascalCase vs Lowercase
// PascalCase: React treats as a custom component
function MyButton(): JSX.Element {
return <button className="custom">Click</button>;
}
// Usage: PascalCase invokes the component
<MyButton />
// Lowercase: React treats as an HTML element
// <myButton /> would render an unknown HTML element, NOT the component above
// Dynamic component selection ALWAYS requires PascalCase variable
const components: Record<string, React.ComponentType> = {
photo: PhotoStory,
video: VideoStory,
};
function Story({ type }: { type: string }): JSX.Element {
const SpecificStory = components[type]; // PascalCase variable
return <SpecificStory />;
}