React Basics #4: Components and Props

7 min read

Last time we looked at JSX syntax. Along the way we naturally saw a function named App. That App is actually React’s most important unit — a component. In this post we’ll cover what a component is and how to build one, plus props, the channel for passing data between components.

Why do we need components? #

Imagine writing the entire screen as one giant function. Header, sidebar, main content, footer, buttons, input fields… your code quickly hits hundreds or thousands of lines, and finding what to fix becomes nearly impossible. If you have ten buttons of the same shape, you’d have to write the same code ten times.

React solves this with the concept of a component. A component is a reusable unit that represents a piece of the screen. Once you’ve defined screen elements like headers, buttons, cards, and input fields each as their own component, you can use them anywhere just like an HTML tag.

Creating your first component #

In React, a component is ultimately a JavaScript function that returns JSX. The App we’ve been seeing is one too.

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

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

export default App;

We define a new function called Greeting and use it inside App like an HTML tag: <Greeting />. A single function becomes a single component, just like that.

Note
Component names must start with an uppercase letter. Starting with lowercase like <greeting /> makes React treat it as a regular HTML tag, leading to unintended behavior. The naming convention: PascalCase (UserCard, LoginButton) is standard.

Splitting components into separate files #

As the number of components grows, splitting them into separate files is better than cramming everything into one. The usual practice is one component per file.

Create a new file src/Greeting.jsx:

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

export default Greeting;

Then in App.jsx, import it:

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

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

export default App;

export default for export and import for import — the standard JavaScript module pattern. You can omit the file extension (.jsx); Vite will find it.

Tip
The convention is to name files in PascalCase to match the component, like Greeting.jsx or UserCard.jsx. The folder structure varies by project, but small projects commonly group components under src/components/.

Passing data to a component — props #

Our Greeting component always prints “Hello, React!”. But what if you want to show a different greeting per user? props is the answer.

Props are like a component’s parameters — the way a parent passes data down to a child. You write them just 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 use Greeting three times, passing a different name value each time. Now let’s update Greeting to receive that value.

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

export default Greeting;

The function’s first parameter is an object called props. Every attribute the parent passed in becomes a property of that object. Since we passed name="Cheolsu", props.name is 'Cheolsu'.

The screen prints:

Output
Hello, Cheolsu!
Hello, Younghee!
Hello, Minsu!

The same component is reused three times with only the props changing. That’s the core value of components.

Various types of props #

You can pass not only strings but numbers, booleans, arrays, objects, even functions as props. Just remember that non-string values must be wrapped in { }.

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>Has admin privileges.</p>}
      <p>Email: {props.profile.email}</p>
      <p>Hobbies: {props.hobbies.join(', ')}</p>
    </div>
  );
}

export default UserCard;

Strings go in quotes (name="Cheolsu"), and any other JavaScript value goes in curly braces (age={30}).

Receiving cleanly with destructuring #

Typing props.name, props.age, and so on gets tedious. Use JavaScript’s destructuring assignment:

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

export default Greeting;

By destructuring at the parameter slot, the function body just uses name directly, and the code shortens. For multiple props, list them all:

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

export default UserCard;

This style is far more common in real React code, and we’ll use it from now on.

Default values #

When a prop might not be provided, you can specify a default with destructuring:

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

Calling <Greeting /> without name automatically uses 'Guest'.

children — content between component tags #

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

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

function App() {
  return (
    <Card>
      <h2>Notice</h2>
      <p>We're closed today.</p>
    </Card>
  );
}

In this case, the content between <Card> and </Card> is automatically passed as a special prop called 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;

Card has no idea what content will go inside, but all it does is render children in that place. This pattern shows up constantly in layout components (Card, Modal, Layout) and wrapper components.

Props are read-only #

Last but certainly not least: a component must never modify the props it receives. The code below is incorrect.

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

Props are a copy of data flowing down from the parent — not a value the child gets to mutate at will. If you need to transform something, store it in a new variable.

Correct
function Greeting({ name }) {
  const upperName = name.toUpperCase();
  return <h1>Hello, {upperName}!</h1>;
}
Note
This rule isn’t merely a style guide; it’s tied directly to how React works. React is built on the assumption that data flows in one direction, from top to bottom (parent → child). If a child mutates props at will, the data flow becomes muddled and debugging gets very hard. When you need to change a parent’s data, use state and callback functions, which we’ll cover next.

Try it yourself #

Restructure the src/App.jsx from last time as follows.

Create 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;

And src/App.jsx:

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;

Save and three member cards appear on screen. The same UserCard component is reused three times with different props. Try changing the names or hobbies, or adding a new card.

Wrapping up #

In this post we built components — React’s core unit — split them into separate files, and learned to pass data using props. We also covered destructuring, default values, children, and the read-only rule. You can now divide a screen into small reusable pieces.

So far every component we’ve built has been static. Once rendered, they never changed. But real apps constantly change in response to user input, time, and server responses. In the next post, “React Basics #5: State and useState,” we’ll learn how a component handles changeable data — the concept of state and the useState hook.

X