Skip to main content
PProfileMatch

React Components and Props

Web DevelopmentLesson 2 of 64 min read

What you'll learn

  • Pass data into a component using props and read it with destructuring
  • Type props with TypeScript and set default values for optional props
  • Compose components using the special children prop
  • Explain one-way data flow and why props are read-only

On this page

  1. Props: the inputs to a component
  2. Default values and optional props
  3. Composition with the children prop
  4. One-way data flow
  5. Common mistakes
  6. How this shows up in interviews and real projects

A single hard-coded profile card is not useful. A card that accepts a name, a role and a list of skills can be reused for every person in your app. Props are how that data gets in.

Props: the inputs to a component

A component is a function, and props are its single argument: an object holding whatever the parent passed in. When you write <ProfileCard name="Asha" />, React calls ProfileCard({ name: "Asha" }). Almost everyone destructures the object in the parameter list so the component body reads naturally.

Props can be any JavaScript value: strings, numbers, booleans, arrays, objects and even functions. Strings can be written in quotes; everything else goes in braces. A boolean prop written without a value, like isOpenToWork, means true.

ProfileCard.tsx
type ProfileCardProps = {
  name: string;
  role: string;
  skills?: string[];
  isOpenToWork?: boolean;
};

export default function ProfileCard({
  name,
  role,
  skills = [],
  isOpenToWork = false,
}: ProfileCardProps) {
  return (
    <article>
      <h3>{name}</h3>
      <p>{role}</p>
      {isOpenToWork ? <strong>Open to work</strong> : null}
      <p>Skills: {skills.length > 0 ? skills.join(", ") : "none listed yet"}</p>
    </article>
  );
}
App.tsx
import ProfileCard from "./ProfileCard";

export default function App() {
  return (
    <main>
      <ProfileCard
        name="Asha Verma"
        role="Frontend Developer"
        skills={["React", "CSS"]}
        isOpenToWork
      />
      <ProfileCard name="Ravi Nair" role="Backend Developer" />
    </main>
  );
}

Output

Asha Verma
Frontend Developer
Open to work
Skills: React, CSS

Ravi Nair
Backend Developer
Skills: none listed yet

Default values and optional props

The ? in skills?: string[] marks a prop as optional, and the = [] in the destructuring supplies a fallback when the parent leaves it out. This is the idiomatic way to set defaults in modern React. You may see Component.defaultProps = {...} in older tutorials, but React 19 no longer supports it for function components, so use default parameter values instead.

Defaults only apply when the value is undefined. If a parent passes null or an empty string on purpose, the default is skipped. Decide per prop whether null should be allowed, and type it accordingly (skills: string[] | null).

Composition with the children prop

Anything you put between a component's opening and closing tags arrives as the special children prop. This lets you build wrapper components, such as a card or a modal, that do not need to know what goes inside them. Composition like this is usually a better fit than building a component with dozens of configuration props.

Card.tsx
import type { ReactNode } from "react";

type CardProps = {
  title: string;
  children: ReactNode;
};

export default function Card({ title, children }: CardProps) {
  return (
    <section style={{ border: "1px solid #ccc", padding: 16, borderRadius: 8 }}>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

// Usage
// <Card title="Candidates">
//   <ProfileCard name="Asha Verma" role="Frontend Developer" />
//   <p>2 more candidates match your filters.</p>
// </Card>

Output

Candidates            (card heading, inside a bordered box)
Asha Verma
Frontend Developer
2 more candidates match your filters.

One-way data flow

Data in React flows down: a parent passes props to its children, never the other way. Props are also read-only. A component must not change the props it receives, just as a pure function must not change its arguments. This rule is what makes React apps predictable: when something looks wrong on screen, you can trace the value upward through the parents until you find where it came from.

So how does a child tell its parent something happened? The parent passes a function as a prop and the child calls it. Data goes down as props, events go up as function calls.

CandidateList.tsx
type Candidate = { id: number; name: string };

type CandidateListProps = {
  candidates: Candidate[];
  onSelect: (name: string) => void;
};

function CandidateList({ candidates, onSelect }: CandidateListProps) {
  return (
    <ul>
      {candidates.map((c) => (
        <li key={c.id}>
          <button onClick={() => onSelect(c.name)}>{c.name}</button>
        </li>
      ))}
    </ul>
  );
}

export default function App() {
  const people: Candidate[] = [
    { id: 1, name: "Asha Verma" },
    { id: 2, name: "Ravi Nair" },
  ];

  return (
    <CandidateList
      candidates={people}
      onSelect={(name) => console.log("Selected", name)}
    />
  );
}

Output

[Asha Verma] [Ravi Nair]      (two buttons)
Console after clicking the second button:
Selected Ravi Nair

Right now the selection is only logged. In React useState Hook: Managing State you will store it in the parent so the UI can react to it.

Common mistakes

  • Mutating a prop. Writing props.skills.push("Go") changes the parent's data directly. Symptom: values change in places you did not expect and updates do not re-render. Copy first: [...skills, "Go"].
  • Passing a string where a number is expected. <Counter start="5" /> passes the string "5", so start + 1 gives "51". Use start={5}.
  • Calling the function instead of passing it. onSelect={handle(name)} runs during render; onSelect={() => handle(name)} runs on click. Symptom: the handler fires immediately or causes an infinite loop.
  • Forgetting that `children` is not required in the type. If you write children?: ReactNode the wrapper may render empty. Decide whether it is mandatory.
  • Prop drilling forever. Passing the same value through five layers is a smell. Try composition first (pass the finished element as children), and reach for context only when many distant components need the same value.

How this shows up in interviews and real projects

"Props vs state" is one of the most common React interview questions. The short answer: props are inputs owned by the parent and read-only for the child, while state is data a component owns and can change. In real code, good component design is mostly deciding what the props of each piece should be. If you want to practise explaining these ideas out loud, the coding interview prep guide for self-taught developers has a useful routine.

Try it yourself

Build a NotificationBell component with a required count prop and an optional label prop that defaults to "Notifications". Wrap two of them in a Panel component that accepts children and a title prop.

Show a hint

Type the props with a type alias, give label a default in the destructuring, and import ReactNode as a type for children.

Key takeaways

  • Props are the single argument of a component function; destructure them in the parameter list.
  • Set defaults with default parameter values, not defaultProps, which React 19 ignores for function components.
  • The children prop lets you build flexible wrapper components through composition.
  • Data flows down as props and events flow up as callback props; never mutate props.

Test yourself

A trick from this lesson's ride: Whatever sits between the tags is the filling that the wrapper serves as children. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

What are props in React?

Props are the inputs a parent passes to a component, delivered as a single object argument. They can hold any JavaScript value, including functions, and the component should treat them as read-only.

What is the difference between props and state in React?

Props are passed in from a parent and cannot be changed by the receiving component. State is private data that a component owns and updates itself using a setter such as the one from useState. Changing either triggers a re-render.

How do I pass a function as a prop in React?

Pass the function reference without calling it, for example onSelect={handleSelect}, or wrap it in an arrow function when you need arguments, such as onSelect={() => handleSelect(id)}. The child then calls it like any other function, usually inside an event handler.