Skip to content
elephantoo

Components & props

Lesson 4 of 30 15 min read

Split UI into components, pass data down with props, children and default values.


Hard-coded components aren't very reusable. Props (short for properties) let a parent pass data into a child — just like arguments to a function. Together with composition, props are how you build big interfaces from small, reusable pieces.

Components are functions#

A component is a JavaScript function that returns JSX. Its name must start with a capital letter:

JSX
function CourseCard() {
  return (
    <article className="card">
      <h3>JavaScript</h3>
      <p>34 lessons</p>
    </article>
  );
}

This always shows "JavaScript". To make it reusable, give it inputs.

Passing and reading props#

Props are written like HTML attributes and arrive as one object — the component's first parameter:

JSX
function CourseCard(props) {
  return (
    <article className="card">
      <h3>{props.title}</h3>
      <p>{props.lessons} lessons</p>
    </article>
  );
}

export default function App() {
  return (
    <>
      <CourseCard title="JavaScript" lessons={34} />
      <CourseCard title="React" lessons={30} />
      <CourseCard title="Python" lessons={32} />
    </>
  );
}
  • Strings can use quotes: title="Java".
  • Everything else uses braces: lessons={34}, featured={true}, tags={["web"]}, onSelect={handleSelect}.
  • A prop with no value is true: <CourseCard featured />.

Destructuring props#

It's idiomatic to destructure right in the parameter list — and to set default values there:

JSX
function CourseCard({ title, lessons, level = "Beginner", featured = false }) {
  return (
    <article className={featured ? "card card-featured" : "card"}>
      <h3>
        {title} {featured && <span aria-label="featured">⭐</span>}
      </h3>
      <p>
        {lessons} lessons · {level}
      </p>
    </article>
  );
}

// <CourseCard title="Linux" lessons={25} />                       → "Beginner"
// <CourseCard title="React" lessons={30} level="Advanced" featured />

The defaults apply when a prop is missing or undefined. (The old Component.defaultProps syntax was removed for function components in React 19.)

Passing objects and arrays#

JSX
const course = {
  slug: "react",
  title: "React",
  lessons: 30,
  tags: ["ui", "frontend"],
};

function CourseSummary({ course }) {
  return (
    <div>
      <h3>{course.title}</h3>
      <ul>
        {course.tags.map((tag) => (
          <li key={tag}>{tag}</li>
        ))}
      </ul>
    </div>
  );
}

function App() {
  return (
    <>
      <CourseSummary course={course} />
      {/* or spread every property as a separate prop: */}
      <CourseCard {...course} />
    </>
  );
}

Spreading ({...course}) is convenient but hides which props a component actually uses. Use it deliberately.

Props are read-only#

A component must never modify its props:

JSX
function Bad({ count }) {
  count = count + 1; // ❌ never modify props
  return <p>{count}</p>;
}

React components must behave like pure functions with respect to their props: same inputs → same output, with no side effects during rendering. Values that change over time live in state (coming soon), usually in a parent that passes them down as props.

The children prop#

Whatever you nest between a component's tags arrives as children:

JSX
function Callout({ tone = "info", title, children }) {
  return (
    <aside className={`callout callout-${tone}`}>
      {title && <strong>{title}</strong>}
      <div>{children}</div>
    </aside>
  );
}

function Lesson() {
  return (
    <Callout tone="tip" title="Tip">
      <p>Components can wrap <em>any</em> JSX.</p>
      <p>Even other components.</p>
    </Callout>
  );
}

children is how you build layout components, cards, modals, panels and page shells — components that provide a frame and let the caller decide the contents.

JSX
function PageLayout({ sidebar, children }) {
  return (
    <div className="layout">
      <nav className="layout-sidebar">{sidebar}</nav>
      <main className="layout-content">{children}</main>
    </div>
  );
}

<PageLayout sidebar={<CourseNav />}>
  <h1>Components & props</h1>
</PageLayout>;

Any prop can hold JSX — sidebar above works like a second, named "slot".

Passing functions as props#

Data flows down via props. To let a child tell its parent something happened, the parent passes a function:

JSX
function LikeButton({ onLike }) {
  return <button onClick={onLike}>👍 Like</button>;
}

function Post({ title }) {
  function handleLike() {
    console.log(`Liked "${title}"`);
  }
  return (
    <article>
      <h2>{title}</h2>
      <LikeButton onLike={handleLike} />
    </article>
  );
}

By convention, props that receive event handlers are named onSomething (onLike, onSelect, onClose), and the functions that handle them are named handleSomething.

Splitting a UI into components#

How do you decide what becomes a component? Look for pieces that:

  • repeat (cards in a grid, rows in a table),
  • have a clear single responsibility (a search box, a price tag),
  • or make a big component easier to read.
JSX
function Avatar({ src, name, size = 48 }) {
  return <img className="avatar" src={src} alt={name} width={size} height={size} />;
}

function UserInfo({ user }) {
  return (
    <div className="user-info">
      <Avatar src={user.avatarUrl} name={user.name} />
      <span>{user.name}</span>
    </div>
  );
}

function Comment({ author, text, date }) {
  return (
    <div className="comment">
      <UserInfo user={author} />
      <p>{text}</p>
      <time dateTime={date.toISOString()}>{date.toLocaleDateString()}</time>
    </div>
  );
}

Each piece is small, named and reusable — Avatar can now appear anywhere.

Exporting and importing components#

Put each component in its own file and import it where needed:

JSX
// src/components/Avatar.jsx
export function Avatar({ src, name, size = 48 }) {
  return <img className="avatar" src={src} alt={name} width={size} height={size} />;
}
JSX
// src/App.jsx
import { Avatar } from "./components/Avatar.jsx";

export default function App() {
  return <Avatar src="/ada.png" name="Ada Lovelace" />;
}

Named exports (export function Avatar) are common for components; Vite's starter uses export default for App. Either works — be consistent.

Don't define components inside components#

JSX
function Parent() {
  // ❌ A new Child "type" is created on every render: its state resets and it's slow
  function Child() {
    return <p>child</p>;
  }
  return <Child />;
}

Always define components at the top level of a module.

Common mistakes#

  • Lowercase component names: <courseCard /> is treated as an unknown HTML tag.
  • Forgetting braces for non-string props: lessons="34" passes a string.
  • Mutating props or objects received via props.
  • Calling a component like a function ({CourseCard()}) instead of rendering it (<CourseCard />) — hooks inside it would break.
  • Passing onClick={handleLike()} (calls it during render) instead of onClick={handleLike}.

What's next#

Next: making components respond to the user with event handlers.

Check your understanding

Quick quiz

0/3 answered
  1. 1.How do props flow in React?

  2. 2.Can a component change its own props?

  3. 3.What is the special children prop?

Finished reading?

Mark this lesson complete to track your progress.