Contents
4 Chapter

Components and props

We build function components and look at the basic pattern for flowing data via props. The foundation for the interfaces we solidify with TypeScript in Chapter 17.

In Chapter 3 we covered JSX syntax. Along the way the function named App showed up often, and in fact this App is React’s most important unit — a component. In this chapter we look at what a component is and how to build one, and then at props, the channel for passing data between components.

The props model from this chapter gets solidified again with TypeScript in Chapter 17 (Typing props and children), and in Chapter 24 (Server vs Client Components) it extends to the serialization constraints that apply when props cross the server → client boundary. Lock down the basics of components / props in this chapter and the later chapters read much more lightly.

Why do we need components? #

Imagine writing the whole screen as one giant function. Header, sidebar, main content, footer, buttons, input fields. The code quickly grows to hundreds, then thousands of lines, and even finding the place to edit becomes hard. If the same button shape appears 10 times on screen, you write the same code 10 times.

React solves this with the concept of a component. A component is a reusable unit that represents one piece of the screen. Build each screen element — header, button, card, input — as a single component, and then use them in the places you need them just like an HTML tag.

Building your first component #

In React, a component is in the end a JavaScript function that returns JSX. The App we have been seeing throughout Chapter 3 is exactly that.

src/App.jsx
function Greeting() {
  return <h1>Hello, React!</h1>;
}

function App() {
  return (
    <div>
      <Greeting />
    </div>
  );
}

export default App;

We defined a new function called Greeting and used it inside App like an HTML tag with <Greeting />. One function becomes one component.

Note
Component names must start with an uppercase letter. If you write <greeting /> with a lowercase start, React treats it as a regular HTML tag and the behavior differs from your intent. Naming convention: PascalCase (UserCard, LoginButton) is the standard.

Splitting components into separate files #

As the number of components grows, splitting them across files works better than cramming them into one. Generally one file per component.

Create a new file src/Greeting.jsx:

src/Greeting.jsx
function Greeting() {
  return <h1>Hello, React!</h1>;
}

export default Greeting;

And in App.jsx, import it:

src/App.jsx
import Greeting from './Greeting';

function App() {
  return (
    <div>
      <Greeting />
    </div>
  );
}

export default App;

The usual JavaScript module pattern of export default to export and import to receive applies as-is. You can drop the file extension (.jsx); Vite finds it on your behalf.

Tip
By convention, the filename matches the component name in PascalCase too: Greeting.jsx, UserCard.jsx, and so on. Folder structure varies by project, but small projects commonly gather components under src/components/.

Passing data into a component — props #

The Greeting component always prints “Hello, React!”. What if we want to show a different greeting per user? Use props.

Props are like a component’s parameters. They are how the parent passes data down to the child component. You write them naturally, like HTML attributes.

src/App.jsx
import Greeting from './Greeting';

function App() {
  return (
    <div>
      <Greeting name="Cheolsu" />
      <Greeting name="Younghee" />
      <Greeting name="Minsu" />
    </div>
  );
}

export default App;

We used Greeting three times, each with a different name value. Now update the Greeting component to receive that value.

src/Greeting.jsx
function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

export default Greeting;

The first parameter of the function is an object called props. Every attribute the parent passed shows up as a property on that object. Since we passed name="Cheolsu", props.name is 'Cheolsu'.

The screen shows:

screen output
Hello, Cheolsu!
Hello, Younghee!
Hello, Minsu!

The same component was reused three times by changing only its props. That is the core value of components.

Various prop types #

You can pass not only strings but also numbers, booleans, arrays, objects, and even functions through props. The only thing to remember is that non-string values must be wrapped in curly braces { }.

src/App.jsx
function App() {
  const user = { name: 'Cheolsu', email: 'cheolsu@example.com' };

  return (
    <UserCard
      name="Cheolsu"
      age={30}
      isAdmin={true}
      hobbies={['reading', 'coding', 'travel']}
      profile={user}
    />
  );
}
src/UserCard.jsx
function UserCard(props) {
  return (
    <div>
      <h2>{props.name} ({props.age})</h2>
      {props.isAdmin && <p>You have admin privileges.</p>}
      <p>Email: {props.profile.email}</p>
      <p>Hobbies: {props.hobbies.join(', ')}</p>
    </div>
  );
}

export default UserCard;

Remember: strings get quotes (name="Cheolsu"), and any other JavaScript value goes inside curly braces (age={30}).

Receiving cleanly with destructuring #

Writing props.name, props.age, prefixing props. every time gets tiresome, so use JavaScript destructuring.

src/Greeting.jsx
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

export default Greeting;

Destructure right in the parameter list and inside the function body you can use name alone, shortening the code. For multiple props, just list them out.

src/UserCard.jsx
function UserCard({ name, age, isAdmin, hobbies, profile }) {
  return (
    <div>
      <h2>{name} ({age})</h2>
      {isAdmin && <p>You have admin privileges.</p>}
      <p>Email: {profile.email}</p>
      <p>Hobbies: {hobbies.join(', ')}</p>
    </div>
  );
}

export default UserCard;

In real-world React code this form shows up much more often. This book uses this style from here on.

Default values #

When a prop might not be passed, you can give a default value alongside destructuring.

src/Greeting.jsx
function Greeting({ name = 'guest' }) {
  return <h1>Hello, {name}!</h1>;
}

If you use <Greeting /> without name, 'guest' is used automatically.

children — what goes between the tags #

So far we have used components only in self-closing form like <Greeting />. But sometimes, just like in HTML, you want to put something between an opening and closing tag.

src/App.jsx
import Card from './Card';

function App() {
  return (
    <Card>
      <h2>Notice</h2>
      <p>The shop is closed today.</p>
    </Card>
  );
}

What goes between <Card> and </Card> is automatically passed via a special prop named children.

src/Card.jsx
function Card({ children }) {
  return (
    <div className="card" style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '8px' }}>
      {children}
    </div>
  );
}

export default Card;

The Card component does not know what will come inside, but it just renders children at the right spot. This pattern shows up extremely often in layout components (Card, Modal, Layout) and wrapper components.

Chapter 17 (Typing props and children) covers the exact type for children — the differences between ReactNode, ReactElement, and JSX.Element.

Props are read-only #

The most important rule, last. A component must never modify the props it receives. The following is wrong.

bad
function Greeting({ name }) {
  name = name.toUpperCase(); // 🚫 modifying props directly
  return <h1>Hello, {name}!</h1>;
}

Props are a copy of data flowing down from the parent — not a value the child can freely change. If you need a transformation, store it in a new variable.

good
function Greeting({ name }) {
  const upperName = name.toUpperCase();
  return <h1>Hello, {upperName}!</h1>;
}
Note

This rule is not just a style guide; it is directly tied to how React works. React is built on the assumption that data flows in one direction, from top to bottom (parent → child), so when a child freely modifies its received props, the data flow gets tangled and debugging becomes very hard. When you need to change the parent’s data, use the state and callback functions you will learn in Chapter 5.

The rule becomes important again in Chapter 24 (Server vs Client Components). When a Server Component passes props to a Client Component, only values that can be serialized (converted to JSON) are allowed. Functions, Dates, Maps, and the like cannot be passed directly.

Try it yourself #

Change src/App.jsx from before into the following structure.

Create a new src/UserCard.jsx:

src/UserCard.jsx
function UserCard({ name, age, hobbies }) {
  return (
    <div style={{ border: '1px solid #ccc', padding: '12px', margin: '8px', borderRadius: '8px' }}>
      <h2>{name} ({age})</h2>
      <p>Hobbies: {hobbies.join(', ')}</p>
    </div>
  );
}

export default UserCard;

src/App.jsx becomes:

src/App.jsx
import UserCard from './UserCard';

function App() {
  return (
    <>
      <h1>Member list</h1>
      <UserCard name="Cheolsu" age={30} hobbies={['reading', 'coding']} />
      <UserCard name="Younghee" age={28} hobbies={['travel', 'cooking', 'photography']} />
      <UserCard name="Minsu" age={35} hobbies={['gaming']} />
    </>
  );
}

export default App;

On save you get three member cards drawn on the screen. The same UserCard component got reused three times with only its props changing.

Exercises #

  1. Add a new prop isOnline (boolean) to UserCard, and show a 🟢 emoji next to the name only when it is true. Only pass isOnline={true} for Younghee out of the three. Use the {isOnline && '🟢'} pattern for conditional display (we cover this in more depth in Chapter 7).
  2. Build a new Card component that takes children and shows them inside a bordered box. Then use it in App like <Card><h2>Notice</h2><p>The site is under maintenance.</p></Card>.
  3. Add a greeting prop to the Greeting component with the default value 'Hello'. Make <Greeting name="Cheolsu" /> and <Greeting name="John" greeting="Hi" /> print “Hello, Cheolsu!” and “Hi, John!” respectively.

In one line: A component is a JavaScript function that returns JSX. The parent flows data down to the child via props, and the child uses them read-only. Destructuring / default values / the children pattern are the entire daily flow. Never modify props.

Next chapter #

Up to now, all our components have been static. Once rendered, they never change. But real apps shift constantly with user input, time, and server responses. In the next chapter, Chapter 5: State and useState, we learn how a component handles data that can change — the concept of state and the useState hook.

X