JavaScript and React course Β· Module 12: React and TypeScript
Typing Props - Interfaces and Optional Properties
In this lesson5
A crew member card works great until someone forgets to pass the rank or gives the experience as the text "fifteen". Props are cosmic communication between components. TypeScript lets you precisely define what data can be transmitted - it's like a communication protocol on a spaceship: a message that breaks the protocol never even gets sent.
Interfaces for Props
Interfaces are the most common way to define props types in React. The interface name usually ends with Props, and each field matches one attribute the parent passes in JSX. First, the protocol itself:
1// An interface defines the "shape" of data
2interface CrewMemberProps {
3 name: string;
4 rank: string;
5 experience: number;
6 specialization: string;
7}Now we attach it to the component parameter, just like in the previous lesson, and show the usage:
1function CrewMember({ name, rank, experience, specialization }: CrewMemberProps) {
2 return (
3 <div>
4 <h3>{name}</h3>
5 <p>Rank: {rank}</p>
6 <p>Years of experience: {experience}</p>
7 <p>Specialization: {specialization}</p>
8 </div>
9 );
10}
11
12// Usage - TypeScript checks all props
13<CrewMember
14 name="Nova"
15 rank="Captain"
16 experience={15}
17 specialization="Navigation"
18/>Notice experience={15}: numbers go in braces, text goes in quotes. If you left out rank or wrote experience="15", the editor would underline the error before you run anything. The JSX call looks exactly like JavaScript - the protocol works in the background.
Optional Props
Not all props are required. Use the ? sign after the field name to mark a prop as optional. The type of such a field is effectively "a value or undefined". The example also uses a union type, a type made of several allowed values joined with |, and Date, the built-in date type.
1interface MissionProps {
2 title: string; // required
3 description: string; // required
4 priority?: "low" | "medium" | "high"; // optional
5 deadline?: Date; // optional
6 assignedCrew?: string[]; // optional
7}The priority field accepts only one of three strings - the typo "hihg" is rejected. How do you set a default value? The simplest way is in the parameter destructuring, with =:
1function MissionCard({ title, description, priority = "medium", deadline, assignedCrew }: MissionProps) {
2 return (
3 <div>
4 <h3>{title}</h3>
5 <p>{description}</p>
6 <span>Priority: {priority}</span>
7 {deadline && <p>Deadline: {deadline.toLocaleDateString()}</p>}
8 {assignedCrew && (
9 <ul>
10 {assignedCrew.map(member => <li key={member}>{member}</li>)}
11 </ul>
12 )}
13 </div>
14 );
15}Inside the component priority no longer includes undefined, because the default fills the gap. deadline and assignedCrew may be missing, so we check them with && before calling a method on them - TypeScript wouldn't allow it without the check. A note for readers of older guides: defaultProps for function components was removed in React 19, so set defaults only in destructuring.
Both calls below are valid:
1// Can be used without optional props
2<MissionCard title="Recon" description="Sector 7 reconnaissance" />
3
4// Or with all of them
5<MissionCard
6 title="Recon"
7 description="Sector 7 reconnaissance"
8 priority="high"
9 assignedCrew={["Nova", "Ra"]}
10/>Leaving out title or description would be an error, but you can add optional fields in any combination.
Typing children
The children prop is a special prop in React: it receives everything you put between the component's opening and closing tags. The React.ReactNode type covers everything React can render: JSX elements, text, numbers, null and arrays of these values.
1// React.ReactNode - accepts anything React can render
2interface PanelProps {
3 title: string;
4 children: React.ReactNode;
5}
6
7function Panel({ title, children }: PanelProps) {
8 return (
9 <div className="panel">
10 <h2>{title}</h2>
11 <div className="content">{children}</div>
12 </div>
13 );
14}
15
16// Usage
17<Panel title="Mission Data">
18 <p>Status: active</p>
19 <MissionCard title="Recon" description="..." />
20</Panel>The panel doesn't know what it will receive, and it doesn't need to - it takes a paragraph, another card or plain text. If you wanted to accept only JSX elements, react.dev points to the narrower React.ReactElement type, but for panels and containers React.ReactNode is the right choice.
Type vs Interface
You can also use type instead of interface. The type keyword creates an alias, a new name for any type - including a union type, which an interface cannot express.
1// Type alias - alternative to interface
2type ButtonVariant = "primary" | "secondary" | "danger";
3
4type ButtonProps = {
5 label: string;
6 variant: ButtonVariant;
7 disabled?: boolean;
8 onClick: () => void;
9};() => void is the type of a function that takes nothing and returns nothing - that's how we describe callbacks. An interface, on the other hand, can be extended with extends, inheriting every field of the base protocol:
1// Interface - can be extended
2interface BaseProps {
3 id: string;
4 className?: string;
5}
6
7interface ShipProps extends BaseProps {
8 name: string;
9 speed: number;
10}
11
12// ShipProps has: id, className, name, speedMy recommendation: interface for object props, type for unions and aliases. Consistency across the project matters most, though.
Extending Interfaces
Interfaces can be combined and extended to build more complex structures. A field can have another interface as its type, and several interfaces can extend the same base:
1interface Coordinates {
2 x: number;
3 y: number;
4 z: number;
5}
6
7interface SpaceObject {
8 name: string;
9 coordinates: Coordinates;
10}
11
12interface Planet extends SpaceObject {
13 radius: number;
14 habitable: boolean;
15 atmosphere: string[];
16}
17
18interface Star extends SpaceObject {
19 temperature: number;
20 luminosity: number;
21 spectralClass: string;
22}Planet and Star share name and coordinates, and each adds its own fields. A change to SpaceObject immediately affects both. These hierarchies come back in the lesson on discriminated unions, where we teach a component to recognize which object it received.
Code for this lesson: App.tsx
1import React from 'react';
2
3// Interfaces with optional props
4interface Coordinates {
5 x: number;
6 y: number;
7 z: number;
8}
9
10interface SpaceObjectProps {
11 name: string;
12 type: string;
13 coordinates: Coordinates;
14 description?: string; // optional
15 dangerLevel?: 1 | 2 | 3 | 4 | 5; // optional
16 tags?: string[]; // optional
17}
18
19// Component with optional props and default values
20function SpaceObject({
21 name,
22 type,
23 coordinates,
24 description = "No description",
25 dangerLevel = 1,
26 tags = []
27}: SpaceObjectProps) {
28 const dangerColors = ["#22c55e", "#84cc16", "#eab308", "#f97316", "#ef4444"];
29
30 return (
31 <div className="object-card">
32 <h3>{name}</h3>
33 <span className="type-badge">{type}</span>
34 <p>{description}</p>
35 <p className="coords">
36 Position: ({coordinates.x}, {coordinates.y}, {coordinates.z})
37 </p>
38 <div className="danger" style={{ color: dangerColors[dangerLevel - 1] }}>
39 Danger: {"*".repeat(dangerLevel)}{"*".repeat(0)}
40 </div>
41 {tags.length > 0 && (
42 <div className="tags">
43 {tags.map(tag => <span key={tag} className="tag">{tag}</span>)}
44 </div>
45 )}
46 </div>
47 );
48}
49
50// Component with children
51interface PanelProps {
52 title: string;
53 children: React.ReactNode;
54}
55
56function Panel({ title, children }: PanelProps) {
57 return (
58 <div className="panel">
59 <h2>{title}</h2>
60 <div className="panel-content">{children}</div>
61 </div>
62 );
63}
64
65function App() {
66 return (
67 <div className="app">
68 <h1>Cosmic Map</h1>
69 <Panel title="Detected objects">
70 <SpaceObject
71 name="Kepler-442b"
72 type="Planet"
73 coordinates={{ x: 1206, y: 340, z: -89 }}
74 description="Potentially habitable super-Earth"
75 dangerLevel={1}
76 tags={["habitable", "rocky"]}
77 />
78 <SpaceObject
79 name="Betelgeuse"
80 type="Star"
81 coordinates={{ x: 700, y: -200, z: 45 }}
82 dangerLevel={5}
83 />
84 <SpaceObject
85 name="99942 Apophis"
86 type="Asteroid"
87 coordinates={{ x: 0.1, y: 0.02, z: 0 }}
88 description="Near-Earth asteroid"
89 dangerLevel={4}
90 tags={["NEO", "dangerous"]}
91 />
92 </Panel>
93 </div>
94 );
95}
96
97export default App;Remember: a props interface is the communication protocol - whatever doesn't fit the protocol won't pass through the airlock.
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do you mark a prop as optional in a TypeScript interface?
2. What TypeScript type should be used for the children prop to accept any React content?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Typing props with an interface and children
- Horizontal ordering
Arrange the syntax for typing component props using an interface: