Aabha · Frontend foundations

React with TypeScript · Harbor Together

02 · Describe UI with components and props

Start with a UI responsibility

Harbor Together needs the same event summary in several places. Copying its markup means every wording change must be repeated. A component gives that summary a name and a clear input. It is a JavaScript function describing UI, not a new kind of DOM element that erases semantics.

This lesson builds a small event card from typed props. You will predict what renders, choose meaningful component boundaries, and explain why a component must not change its inputs. Open checkpoint 02 after reading the worked example. Keep checkpoint 01 available for comparison.

Describe what varies

Consider one repeated card. Its title and place count vary; its heading structure and availability wording follow a rule. Put those changing values into a props object:

type EventCardProps = {
  title: string;
  places: number;
};

function EventCard({ title, places }: EventCardProps) {
  const availability = places === 0 ? 'Full' : `${places} places`;
  return (
    <article>
      <h2>{title}</h2>
      <p>{availability}</p>
    </article>
  );
}

export default function App() {
  return (
    <main>
      <h1>Harbor Together events</h1>
      <EventCard title="Garden morning" places={7} />
      <EventCard title="Repair café" places={0} />
    </main>
  );
}

Read the type before the markup. Each card requires text and a number. The function destructures those two properties. A caller passes a string with quotes and a numeric expression with braces. Writing places="7" supplies text; TypeScript reports that it does not meet the contract. This is a useful error because comparisons and arithmetic should not depend on accidental string coercion.

The component computes availability from its inputs each time React calls it. There is no need to store a second copy of that sentence. An event with zero places should show “Full,” rather than disappear. Notice the explicit equality check. In JSX, places && <p>Available</p> can render the number zero. Prefer a boolean condition or a conditional expression when zero is meaningful.

JSX is an expression, HTML is still the output

JSX lets the function return a description of elements. It has a few syntax differences from HTML: use className for a CSS class, htmlFor for a label association, close elements such as input, and return one enclosing expression. A fragment, written <>...</>, groups siblings without adding a wrapper element to the DOM. Use it when an extra div would exist only to satisfy the syntax.

Braces contain expressions, such as a property, a function call that calculates a value, or an array of elements. A statement like an if block belongs before return, not directly between braces. A literal string that contains angle brackets remains text when rendered as an ordinary value. Avoid raw HTML injection. Escaping text does not make arbitrary URL values safe, so do not treat it as a complete security policy.

Read the resulting HTML structure in the browser’s inspector. The heading levels should still make sense. An EventCard name does not excuse a clickable div or a missing label. If the card contains a link, make its text identify the destination. Use a button for an action and a link for navigation.

Props are inputs, not an editing surface

Imagine a printed event card made from a record. Displaying it should not reduce the record’s capacity. Similarly, rendering a component should not mutate props or increment an external counter. With the same props and state, the component should calculate the same description. That predictability lets React render again without causing real-world actions.

The parent sends event values into a card; the card describes semantic elements without changing the event record.

Do not call EventCard(...) yourself as a substitute for <EventCard />. Let React manage components as components. Define component functions at module scope, rather than inside another component’s render. A nested definition creates a new function identity on each render and can unexpectedly reset a child’s state when the parent updates. We will make that consequence visible in lesson 05.

Props can also carry a callback or other React content. A wrapper can receive children, whose type is commonly ReactNode. You do not need a generic “universal card” with fifteen options before the second real use case appears. Start with an event-specific component. Extract a shared shell only when the repeated responsibility is clear.

Choose boundaries you can explain

One component per HTML tag creates many files without reducing complexity. One component for the entire application makes responsibilities hard to inspect. A useful boundary has a reason: EventCard explains one event, SearchControls gathers filter input, and EventList presents a result set. A component may be worthwhile even when used only once if it isolates a coherent responsibility.

Checkpoint 02 separates event display from page assembly. Trace a value from the parent into a heading. Change one card title and confirm that another stays unchanged. Then temporarily provide the wrong prop name in your own copy. The type-check should point to the caller. Fix the call rather than broadening the props type to allow arbitrary values.

Practice a rendering trace

Before running the example above, write its two availability strings. Then change the second count from zero to two and predict what changes in the DOM. The parent supplies new information; the card calculates a new description; React commits differences. “The function ran” does not mean every DOM node was destroyed and recreated.

You are ready to continue when you can state the card’s contract, distinguish props from local calculations, and explain the preserved HTML semantics. Complete the exercises. Use the worked solutions only after recording your own prediction and repair.

Primary reading: components, props, pure components, render and commit.