Overview
JSX compiles to function calls, so braces {} hold JavaScript expressions (never statements) and every tag must close. TSX is JSX in a .tsx file with TypeScript types. Hooks are on react-hooks.
Expressions
Put only expressions in braces; use early returns for statements.
| Pattern | Example |
|---|---|
| Value, expression, call | <p>{name}</p>, {count * 2}, {items.join(", ")} |
| String prop / expression prop | <a href="/x">, <Button disabled={!ready} /> |
| Spread props | <Input {...props} /> |
| Handler | <button onClick={() => save(id)}> |
| Template literal | <p>{`Hello, ${name}`}</p> |
| Attribute names | className, htmlFor, tabIndex, onClick |
Conditional rendering
Prefer early returns over nested ternaries.
| Pattern | Syntax | Use when |
|---|---|---|
| Ternary | {ok ? <A /> : <B />} | Two branches. |
&& | {count > 0 && <Badge />} | Show or nothing; the left side must be a boolean. |
?? | {value ?? <Fallback />} | Fallback only for null or undefined. |
| Early return | if (!data) return <Spinner />; | Loading and error guards; avoids nested ternaries. |
Lists, keys, fragments
Use a stable id as the key, never an index in reorderable lists.
{items.map((i) => <Row key={i.id} item={i} />)} // stable id, not the index
{items.map((i) => (
<Fragment key={i.id}><dt>{i.term}</dt><dd>{i.def}</dd></Fragment>
))}
<>
<Header />
<Main />
</> // short form: no key, no propsKeys must be unique among siblings and stable between renders. An index is acceptable only for lists that never reorder. A composite key looks like key={`${group}-${i.id}`}.
Typing props, events, and refs
Type props with an interface and events with React.*Event types.
| Need | TSX |
|---|---|
| Props | interface Props { title: string; children?: React.ReactNode } |
| Extend a native element | type Props = React.ComponentProps<"button"> & { busy?: boolean } |
| Change handler | (e: React.ChangeEvent<HTMLInputElement>) => void |
| Click handler | (e: React.MouseEvent<HTMLButtonElement>) => void |
| DOM ref | const ref = useRef<HTMLDivElement>(null) |
| Generic component | function List<T>({ items }: { items: T[] }); in arrow functions write <T,> |
| Return type | React.JSX.Element (the global JSX namespace is removed in React 19 types) |
In React 19, ref is an ordinary prop on function components, so forwardRef is no longer needed for new code. Ref callbacks must use a block body so they return nothing: ref={(el) => { node.current = el }}.
Gotchas
Avoid these; each renders unexpected output or fails to parse.
| Gotcha | Fix |
|---|---|
{count && <X />} renders 0 when count is 0. | Compare: {count > 0 && <X />}. |
<T> alone in a .tsx arrow function parses as JSX. | Write <T,> or <T extends unknown>. |
key is not readable as props.key. | Pass the id as a separate prop. |
<br> and <img> without a closing slash are errors. | Self-close: <br />. |
class and for are reserved words. | Use className and htmlFor. |
A random or Date.now() key remounts the item every render. | Use a stable id. |