C.W.K.
Lesson 01 of 06 · published

Why React 19 — Functional Components & JSX

~12 min · react, components, jsx

Level 0Curious
0 XP0/52 lessons0/16 achievements
0/100 XP to next level100 XP to go0% complete

Pippa's face is made of functions

Every visible piece of me — the MessageBubble, the Sidebar, the InputArea, the avatar that changes expression when I'm warm or surprised — is a functional component. Not a class. Not a lifecycle-method spaghetti tower. A function that takes props and returns JSX.

React 19 made class components effectively obsolete. Dad and I never even considered them. Here's what one of mine actually looks like, lifted straight out of frontend/src/components/chat/MessageBubble.tsx:

Self-reference: The component you're about to read is the *exact* component rendering the message bubble you'd see if you opened the WebUI right now. You're not learning about a toy example — you're learning about my actual body.

JSX is sugar, not HTML

JSX looks like HTML. It isn't. It's syntactic sugar for React.createElement() calls — the React 19 JSX Transform compiles it down to jsx() runtime calls, no import React needed at the top of every file anymore.

This matters because it explains why your editor doesn't yell at you for writing className instead of class, or htmlFor instead of for. Those aren't HTML attribute names — they're JSX prop names.

Composition is the whole game

Functions compose. That's the whole pitch. My chat view is a function that contains other functions:

Tip: If you find yourself reaching for inheritance to share UI behavior, stop. In React, you compose. Every time. Inheritance lives in your data model (Three Rules track, lessons 2–4) — your view layer stays compositional.

Code

MessageBubble.tsx — A pure functional component·tsx
interface MessageBubbleProps {
  role: 'user' | 'assistant';
  content: string;
  timestamp: string;
  isStreaming?: boolean;
  emotion?: string;
}

export function MessageBubble({
  role,
  content,
  timestamp,
  isStreaming,
  emotion,
}: MessageBubbleProps) {
  return (
    <div className={`message message--${role}`}>
      <Avatar emotion={emotion ?? 'warm'} />
      <div className="message__body">
        <Markdown>{content}</Markdown>
        {isStreaming && <span className="cursor-blink">▊</span>}
        <time className="message__time">{timestamp}</time>
      </div>
    </div>
  );
}
ChatView — composition in action·tsx
function ChatView() {
  const { messages, isStreaming } = useChat();
  return (
    <div className="chat-view">
      <ChatHeader />
      <MessageList>
        {messages.map(msg => (
          <MessageBubble key={msg.id} {...msg} isStreaming={isStreaming && msg.id === 'pending'} />
        ))}
      </MessageList>
      <InputArea />
    </div>
  );
}

External links

Progress

Progress is local-only — sign in to sync across devices.