React Part 3: Components and Props: Building Reusable UI

React Part 3 banner: an App component passing props down to reusable Card components
PART 3 React Series · DevDojo Components and Props Building Reusable UI Write a component once, reuse it everywhere with different data <App /> <Cardtitle=“Profile”/> … <Cardtitle=“Skills”/> props{ title, children }Data flows down, read-only devdojo.co.in · Beginner friendly · React 19 + Vite 8
Series: React Part 3 of 30

What you should know first: read React Part 1: What Is React and How to Build Your First React App and React Part 2: JSX Explained. You should be able to start a Vite project and write simple JSX with curly braces.

Every React app, from a small to-do list to Instagram’s web app, is built from one idea: React components and props. A component is a small, reusable piece of UI, like a button, a card or a whole page section. Props are the inputs you give a component so the same piece of UI can show different data. In this third part of the React series you will write your first components, split a page into a component tree, pass data with props, use the special children prop and build a small product list with a reusable ProductCard. Every example was run with React 19 and the output you see is real.

Quick Summary
  • A component is a JavaScript function whose name starts with a capital letter and which returns JSX. You use it like a tag: <Greeting />.
  • Props are the attributes you write on that tag. React collects them into one object and passes it as the first argument of the function.
  • Destructure props in the function signature: function UserCard({ name, city = "Remote" }). The = "Remote" part is a default value.
  • Props are read-only. Data flows one way, from parent to child. A child never changes its props.
  • The children prop holds whatever you put between the opening and closing tags, which lets you build wrapper components such as Card or Layout.
  • You can pass any value as a prop: text, numbers, booleans, objects, arrays, other JSX and functions.
Full React roadmap (30 parts)

Beginner

  1. What Is React and How to Build Your First React App
  2. JSX Explained: Writing HTML-Like Code in JavaScript
  3. Components and Props: Building Reusable UI You are here
  4. State with useState: Making Components Interactive Coming soon
  5. Handling Events and Forms in React Coming soon
  6. Conditional Rendering and Lists with Keys Coming soon
  7. Styling React Apps: Plain CSS, CSS Modules and Tailwind Coming soon
  8. useEffect: Side Effects, Data Fetching and Cleanup Coming soon
  9. Lifting State Up: Sharing Data Between Components Coming soon
  10. Mini Project: Build a To-Do App with React Coming soon

Intermediate

  1. useRef: Working with the DOM and Keeping Values Coming soon
  2. useContext: Sharing Data Without Prop Drilling Coming soon
  3. useReducer: Managing Complex State Coming soon
  4. Custom Hooks: Reusing Logic Across Components Coming soon
  5. React Router: Pages, Links and Dynamic Routes Coming soon
  6. Fetching Server Data with TanStack Query Coming soon
  7. Forms the Modern Way: Actions, useActionState and Validation Coming soon
  8. React with TypeScript: Typing Props, State and Events Coming soon
  9. Global State: Context vs Zustand vs Redux Toolkit Coming soon
  10. Testing React Apps with Vitest and React Testing Library Coming soon

Advanced

  1. How Rendering Works: Re-renders, Reconciliation and Keys Coming soon
  2. Performance: memo, useMemo, useCallback and the React Compiler Coming soon
  3. Code Splitting with lazy() and Suspense Coming soon
  4. Concurrent React: useTransition and useDeferredValue Coming soon
  5. Error Boundaries and Handling Errors Gracefully Coming soon
  6. The use() API and Optimistic UI with useOptimistic Coming soon
  7. React Server Components and Server Functions Coming soon
  8. Accessibility in React Apps Coming soon
  9. React Patterns for Large Apps: Folder Structure and Composition Coming soon
  10. Final Project: Build and Deploy a Complete React App Coming soon

What are React components?

Look at any website and you will see repeated pieces: the same kind of card for every product, the same button style everywhere, the same header on every page. In plain HTML you copy and paste that markup again and again. When the design changes, you have to fix every copy.

A React component solves this. You write the markup once, inside a function, give it a name and then reuse it as many times as you like. The official React docs describe components as “JavaScript functions that you can sprinkle with markup” (see Your First Component on react.dev).

ReusableWrite a Button once and use it on every page.
Easy to read<Header /><ProductList /><Footer /> tells you the page layout at a glance.
Easy to changeFix a bug in ProductCard and every card on the site is fixed.
Easy to testSmall components with clear inputs are simple to test later in the series.
Function components only. Older tutorials show class components (class App extends React.Component). They still work, but all new React code, and this whole series, uses function components with hooks.

Your first component

Start with the Vite project from Part 1, or create a fresh one:

Terminal — react-components
# Start from the Vite + React project you made in Part 1
$ npm create vite@latest react-components -- --template react
$ cd react-components && npm install
# Create a folder for your components
$ mkdir src/components
$ npm run dev
# Open the local URL Vite prints (usually http://localhost:5173)

A component needs only three things: a function, a name that starts with a capital letter, and a return with JSX. Put each component in its own file and export it so other files can import it.

Greeting.jsx — react-components — Visual Studio Code
react-components › src › Greeting.jsx
// A component is a JavaScript function that returns JSX
export default function Greeting() {
  return <h2 className="greeting">Hello from my first component!</h2>;
}
⎇ mainLn 4, Col 1Spaces: 2UTF-8LFJavaScript JSX
react-components › src › App.jsx
import Greeting from "./Greeting.jsx";

export default function App() {
  return (
    <main>
      <h1>My React App</h1>
      <Greeting />
      <Greeting />
    </main>
  );
}
TERMINALOUTPUTPROBLEMSReal output (React 19.3, esbuild 0.28, Node.js 22)
$ npx esbuild print.jsx --bundle --platform=node --jsx=automatic --outfile=out.js && node out.js
<main>
<h1>My React App</h1>
<h2 class="greeting">Hello from my first component!</h2>
<h2 class="greeting">Hello from my first component!</h2>
</main>
⎇ mainLn 11, Col 1Spaces: 2UTF-8LFJavaScript JSX
react-components › src › print.jsx
import { renderToStaticMarkup } from "react-dom/server";
import App from "./App.jsx";

console.log(renderToStaticMarkup(<App />).replaceAll("><", ">\n<"));
⎇ mainLn 4, Col 1Spaces: 2UTF-8LFJavaScript JSX

Click the three tabs to see each file, then open App.jsx and click ▶ Run to see the HTML that React creates.

A few things to notice:

  • Greeting starts with a capital G. React uses this to tell your components apart from HTML tags. <greeting /> (lowercase) would be treated as an unknown HTML tag.
  • export default makes the component the main export of the file, and import Greeting from "./Greeting.jsx" brings it in.
  • We used <Greeting /> twice and got two headings. That is reuse in its simplest form.
  • The print.jsx file is only for this article: it uses renderToStaticMarkup from react-dom/server to print the HTML in the terminal. In your Vite app, main.jsx renders App into the browser as you saw in Part 1.

If you want to try the terminal trick yourself, this is the exact command we used for every example in this post:

Terminal — print React output in Node.js
# Optional: see the HTML a component makes, without a browser
$ npm install -D esbuild
$ npx esbuild print.jsx --bundle --platform=node --jsx=automatic --outfile=out.js && node out.js
Tip: Never define a component inside another component. Always define them at the top level of a file. A component created inside another one is re-created on every render, which resets its state and slows your app down.

Thinking in components

Before you write code for a new screen, draw boxes around each part of the design and give each box a name. A box that appears several times, or that has one clear job, is a good component. Boxes inside boxes become child components. The result is a component tree with App at the top.

The page you see Header ProductList ProductCard ProductCard ProductCard Footer The component tree App Header ProductList Footer ProductCard ProductCard ProductCard One ProductCard component, used three times with different props (name, price, stock).
Thinking in components: split the page into boxes, give each box a name, and the boxes become a tree with App at the top.

A simple rule from the React team: a component should ideally do one thing. If it grows too big, split it into smaller children. For the page above, App renders Header, ProductList and Footer, and ProductList renders one ProductCard per product. But how does each card know which product to show? That is where props come in.

What are props in React?

Props (short for “properties”) are the inputs of a component. You pass them exactly like HTML attributes. React collects all of them into one object and hands it to your function as its first argument. Think of a component as a function and props as its arguments: <UserCard name="Asha" /> is like calling UserCard({ name: "Asha" }).

UserCard.jsx — react-components — Visual Studio Code
react-components › src › UserCard.jsx
// Props arrive as ONE object. We destructure the keys we need.
function UserCard({ name, role, city = "Remote" }) {
  return (
    <div className="user-card">
      <h3>{name}</h3>
      <p>{role} · {city}</p>
    </div>
  );
}

export default function App() {
  return (
    <section>
      <UserCard name="Asha" role="Frontend Developer" city="Pune" />
      <UserCard name="Rahul" role="Backend Developer" city="Bengaluru" />
      <UserCard name="Meera" role="Designer" />
    </section>
  );
}
TERMINALOUTPUTPROBLEMSReal output (React 19.3, Node.js 22, printed with renderToStaticMarkup)
$ npx esbuild print.jsx --bundle --platform=node --jsx=automatic --outfile=out.js && node out.js
<section>
<div class="user-card">
<h3>Asha</h3>
<p>Frontend Developer · Pune</p>
</div>
<div class="user-card">
<h3>Rahul</h3>
<p>Backend Developer · Bengaluru</p>
</div>
<div class="user-card">
<h3>Meera</h3>
<p>Designer · Remote</p>
</div>
</section>
⎇ mainLn 19, Col 1Spaces: 2UTF-8LFJavaScript JSX

Click ▶ Run. Notice Meera gets the default city “Remote” because no city prop was passed.

Here the function signature uses destructuring: { name, role, city = "Remote" } pulls those three keys out of the props object. It is the same as writing function UserCard(props) and then props.name, but shorter and easier to read. The = "Remote" part is a default value; it is used only when the prop is missing (or undefined).

Heads up: React 19 no longer supports the old Component.defaultProps = {...} for function components. Use default values in destructuring, as shown above.

Props are read-only

Props belong to the parent. The child can read them, but it must never change them. This is called one-way data flow: data moves down the tree, from parent to child, and that makes apps much easier to understand and debug.

1. Parent writes attributes <UserCard name=“Asha” city=“Pune” /> 2. React builds one object props = { name: “Asha”, city: “Pune” } 3. Child reads (read-only) function UserCard( { name, city }) { … } props.name = “x” → TypeError Data flows down from parent to child. To change it, the parent passes new props.
One-way data flow: the parent owns the data, the child only reads its props.

React even protects you here. In development mode the props object is frozen, so trying to change it throws an error:

Badge.jsx — react-components — Visual Studio Code
react-components › src › Badge.jsx
function Badge(props) {
  props.label = "Changed!"; // ❌ never change props
  return <span>{props.label}</span>;
}

function FixedBadge({ label }) {
  const text = label.toUpperCase(); // ✅ make a new value instead
  return <span>{text}</span>;
}
TERMINALOUTPUTPROBLEMSReal output (React 19.3 development build, ES module)
$ NODE_ENV=development node out.mjs
TypeError: Cannot assign to read only property 'label' of object '#<Object>'
⎇ mainLn 9, Col 1Spaces: 2UTF-8LFJavaScript JSX

Click ▶ Run to see the error React throws in development mode when a component changes its props.

So what do you do when something should change, like a counter or a “liked” button? You use state, which is exactly what we cover in the next part. For now remember: props are the settings a parent gives, state is the memory a component keeps for itself.

The children prop: building wrapper components

Sometimes you want a component that wraps other content, like a card, a modal or a page layout. Whatever you place between the opening and closing tags arrives in a special prop called children.

Card.jsx — react-components — Visual Studio Code
react-components › src › Card.jsx
// "children" is whatever you put between <Card> and </Card>
function Card({ title, children }) {
  return (
    <div className="card">
      <h3 className="card-title">{title}</h3>
      <div className="card-body">{children}</div>
    </div>
  );
}

export default function App() {
  return (
    <>
      <Card title="Profile">
        <p>Asha is learning React.</p>
      </Card>
      <Card title="Skills">
        <ul>
          <li>HTML</li>
          <li>JavaScript</li>
        </ul>
      </Card>
    </>
  );
}
TERMINALOUTPUTPROBLEMSReal output (React 19.3, Node.js 22)
$ npx esbuild print.jsx --bundle --platform=node --jsx=automatic --outfile=out.js && node out.js
<div class="card">
<h3 class="card-title">Profile</h3>
<div class="card-body">
<p>Asha is learning React.</p>
</div>
</div>
<div class="card">
<h3 class="card-title">Skills</h3>
<div class="card-body">
<ul>
<li>HTML</li>
<li>JavaScript</li>
</ul>
</div>
</div>
⎇ mainLn 25, Col 1Spaces: 2UTF-8LFJavaScript JSX

Click ▶ Run to see how the content between the tags lands inside card-body.

The Card does not care what is inside it: a paragraph, a list, even other components. This pattern is called composition, and it is how most layout components (Layout, Modal, Panel) are built. You will use it everywhere.

What can you pass as a prop?

Almost anything. Click through the tabs to see the four kinds of values you will use most often:

Strings use quotes, everything else uses curly braces. Write title="Hello" for plain text. For numbers and any JavaScript expression use braces: price={899}, total={price * qty}. If you write price="899" the child receives the text “899”, not a number, and maths like price + 1 gives “8991”.

Booleans switch things on and off. Writing just the name, like <ProductCard showBadge />, is the same as showBadge={true}. Leave it out and the value is undefined, which acts like false. Use showBadge={false} when you want to be clear. Common uses: disabled, isActive, isOpen.

Pass whole objects and arrays. product={product} sends one object, so the child can read product.name and product.price. Arrays work the same: tags={["usb", "office"]}. Notice the double braces in style={{ color: "red" }}: the outer pair means “JavaScript here”, the inner pair is the object. You can also spread an object into props with <UserCard {...user} />.

Functions let the child talk back to the parent. The parent passes onBuy={handleBuy}; the child calls onBuy(name) when the button is clicked. By convention, props that are functions start with on (onClick, onSave), and the parent’s handler starts with handle. Pass the function itself, not handleBuy(), or it runs immediately during render.

Build a reusable ProductCard

Let’s put everything together. One ProductCard component receives a product object, a function prop and a boolean prop. The parent App loops over an array with map() (you saw this in Part 2) and renders one card per product. Each card gets a key so React can track it; we go deeper into keys in Part 6.

ProductCard.jsx — mini-shop — Visual Studio Code
mini-shop › src › components › ProductCard.jsx
export default function ProductCard({ product, onBuy, showBadge = false }) {
  const { name, price, inStock, tags } = product;

  return (
    <article className="product">
      {showBadge && <span className="badge">Best seller</span>}
      <h3>{name}</h3>
      <p className="price">₹{price.toLocaleString("en-IN")}</p>
      <p>{inStock ? "In stock" : "Out of stock"}</p>
      <p className="tags">{tags.join(", ")}</p>
      <button disabled={!inStock} onClick={() => onBuy(name)}>
        Buy now
      </button>
    </article>
  );
}
⎇ mainLn 16, Col 1Spaces: 2UTF-8LFJavaScript JSX
mini-shop › src › App.jsx
import ProductCard from "./ProductCard.jsx";

const products = [
  { id: 1, name: "Wireless Mouse", price: 899, inStock: true, tags: ["usb", "office"] },
  { id: 2, name: "Mechanical Keyboard", price: 3499, inStock: true, tags: ["rgb", "gaming"] },
  { id: 3, name: "4K Monitor", price: 24999, inStock: false, tags: ["display"] },
];

function handleBuy(name) {
  console.log(`Added ${name} to cart`);
}

export default function App() {
  return (
    <div className="shop">
      {products.map((p) => (
        <ProductCard key={p.id} product={p} onBuy={handleBuy} showBadge={p.id === 2} />
      ))}
    </div>
  );
}
TERMINALOUTPUTPROBLEMSReal output (React 19.3, Node.js 22)
$ npx esbuild print.jsx --bundle --platform=node --jsx=automatic --outfile=out.js && node out.js
<div class="shop">
<article class="product">
<h3>Wireless Mouse</h3>
<p class="price">₹899</p>
<p>In stock</p>
<p class="tags">usb, office</p>
<button>Buy now</button>
</article>
<article class="product">
<span class="badge">Best seller</span>
<h3>Mechanical Keyboard</h3>
<p class="price">₹3,499</p>
<p>In stock</p>
<p class="tags">rgb, gaming</p>
<button>Buy now</button>
</article>
<article class="product">
<h3>4K Monitor</h3>
<p class="price">₹24,999</p>
<p>Out of stock</p>
<p class="tags">display</p>
<button disabled="">Buy now</button>
</article>
</div>
⎇ mainLn 21, Col 1Spaces: 2UTF-8LFJavaScript JSX

Open the App.jsx tab and click ▶ Run. The disabled button and the “Best seller” badge both come from props.

Look at the output: the same component produced three different cards. The price is formatted in Indian style with toLocaleString("en-IN"), the out-of-stock monitor has disabled on its button, and only the keyboard shows the badge. In the browser, clicking “Buy now” calls handleBuy in the parent, which logs Added Mechanical Keyboard to cart in the console.

To use it in your Vite app, save ProductCard.jsx in src/components/, put the App code in src/App.jsx (adjust the import path to ./components/ProductCard.jsx) and run npm run dev.

Common errors and fixes

Problem or messageWhy it happensFix
Component does not show, warning “The tag <greeting> is unrecognized in this browser”Component name starts with a lowercase letterRename to Greeting (PascalCase)
“does not provide an export named ‘default'”The file uses a named export (export function Card) but you import it as defaultUse import { Card } from "./Card.jsx", or add export default
“Cannot read properties of undefined (reading ‘name’)”The parent forgot to pass the product prop, or misspelled itCheck the prop name on both sides; add a default or a guard
“Cannot assign to read only property”The component tried to change its propsMake a new variable, or move the value into state (Part 4)
Prop is undefined even though you passed itYou wrote function Card(title) instead of function Card({ title })Add the curly braces to destructure, or use props.title
Function runs on load, or “Too many re-renders”You passed onBuy={handleBuy()} (called it) instead of the functionPass onBuy={handleBuy} or onBuy={() => handleBuy(id)}
“Each child in a list should have a unique key prop”Rendering an array with map() without keyAdd key={item.id} to the outer element

Best practices for React components and props

  • One component per file, file name equals component name: ProductCard.jsx exports ProductCard.
  • Keep components small and focused on one job. If you scroll a lot inside one component, split it.
  • Destructure props in the signature and give defaults for optional ones.
  • Name props clearly: isOpen, hasError for booleans; onSomething for functions.
  • Pass only what a component needs. Prefer name and price over a huge object with fifty fields when the card uses two.
  • Use children for layout instead of many props like bodyText, bodyList, bodyImage.
  • Keep components pure: same props in, same JSX out. No changing props or outside variables during render. This also lets the React Compiler optimise your code automatically later.

Practice task

Build a small “Team” page in your Vite project:

  1. Create src/components/Avatar.jsx that takes name and size (default 48) and shows a circle with the person’s first letter.
  2. Create TeamMemberCard.jsx that takes name, role and isLead, renders <Avatar /> and shows a “Team lead” label only when isLead is true.
  3. Create a Section wrapper that takes a title and uses children.
  4. In App.jsx, store four team members in an array and render them inside <Section title="Our team"> with map() and a key.
  5. Bonus: add an onSayHi function prop that logs “Hi, <name>!” when a card’s button is clicked.

FAQ

What is the difference between a component and an element?

A component is the function you write, such as Greeting. An element is the small object that is created when you use it, <Greeting />. One component can produce many elements.

Why must component names start with a capital letter?

JSX uses the first letter to decide: lowercase tags like <div> are HTML elements, capitalised tags like <Card> are your components. A lowercase component name is treated as an unknown HTML tag.

Can a child component change its props?

No. Props are read-only. If a value needs to change, the parent keeps it in state and passes the new value down, or passes a function prop that the child calls.

What is prop drilling?

Passing a prop through many layers of components that do not use it, just to reach a deep child. It is fine for two or three levels. For deeper trees, React offers useContext, covered in Part 12.

Should I use PropTypes in React 19?

No. React 19 removed PropTypes checks for function components. For type safety use TypeScript, which we cover in Part 18 of this series.

Named export or default export?

Both work. Default exports are common for one main component per file. Named exports are handy when a file has several small components. Just make the import match the export.

Conclusion

You now know the two building blocks behind every React app. React components and props let you write a piece of UI once and reuse it with different data. You learned how to create and export a component, how to split a page into a component tree, how props arrive as one read-only object, how to set default values, how the children prop lets you build wrappers, and how to pass objects, booleans and functions to build a real ProductCard.

Need a refresher on curly braces and the JSX rules? Go back to React Part 2: JSX Explained. Want to read more on the official docs? See Passing Props to a Component on react.dev.

Next part – React Part 4: State with useState: Making Components Interactive. Props come from the parent, but what about data that changes when a user clicks? We will add memory to components with useState, build a counter and a like button, and learn why React re-renders.

New posts every day on DevDojo

Follow the full React series – 30 parts from “what is React” to building and deploying a complete app. Bookmark devdojo.co.in and share your Practice task solution in the comments.

Share