Runnable lesson · press Try it on the examples to edit and run them here.
The big promise of React is reuse: write a piece of UI once and use it everywhere. This lesson shows how to get there from repeated code.
Step 1: notice repetition
Look at this page. The three buttons are nearly the same.
function App() {
return (
<div style={{ display: "flex", gap: 8 }}>
<button style={{ padding: "6px 14px", borderRadius: 6, border: "1px solid #333", background: "#fff" }}>Save</button>
<button style={{ padding: "6px 14px", borderRadius: 6, border: "1px solid #333", background: "#fff" }}>Edit</button>
<button style={{ padding: "6px 14px", borderRadius: 6, border: "1px solid #333", background: "#fff" }}>Delete</button>
</div>
);
}
If the design changes, you must edit three places, and you will forget one.
Step 2: extract a component
Move the repeated markup into a function. What differs (the label) becomes a prop. Props are like function arguments; Understanding Props covers them fully.
function Button({ label }) {
return (
<button style={{ padding: "6px 14px", borderRadius: 6, border: "1px solid #333", background: "#fff" }}>
{label}
</button>
);
}
function App() {
return (
<div style={{ display: "flex", gap: 8 }}>
<Button label="Save" />
<Button label="Edit" />
<Button label="Delete" />
</div>
);
}
Now a design change happens in one place. The component also gives the markup a name, which makes App read like a description.
What makes a component reusable
1. One clear job. Button renders a button. It does not also fetch data and manage a form. If you cannot describe the component in one short sentence, it may be doing too much.
2. Data comes in through props. Do not hard-code text, URLs or numbers that change from place to place.
3. Behaviour comes in through props too. Let the parent decide what happens on click:
function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
function App() {
const [likes, setLikes] = React.useState(0);
return (
<div>
<Button label="Like" onClick={() => setLikes(likes + 1)} />
<p>Liked {likes} time{likes === 1 ? "" : "s"}</p>
</div>
);
}
The same Button can now save, delete or like, depending on the function it receives.
4. Sensible defaults. A Button that works with only a label is easier to use than one that needs ten props. See Default Prop Values.
5. It does not know where it lives. A reusable component should not rely on being inside a certain page or on global variables.
When not to extract
Do not create a component for a single use of three lines of markup that never changes. Extract when:
- the markup is repeated,
- a piece has its own state or logic,
- or the parent has become hard to read.
Too many tiny components with unclear jobs are as hard to follow as one giant one.
Common mistakes
- Props for everything. A component with fifteen props is a sign it should be split, or that some props belong together in an object or as
children. - Copying a component instead of adding a prop for the difference.
- Defining a component inside another component’s body. The inner one is recreated on every render, which resets its state. Define components at the top level.
Key points
- Extract repeated markup into a component; turn the differences into props.
- A reusable component has one job, gets data and behaviour from props and does not depend on its surroundings.
- Extract when there is repetition, separate state or poor readability, not by reflex.
Exercise: A reusable Badge
Create a Badge component that takes a text and a color prop and shows a small coloured label. Use it three times with different values. —solution
function Badge({ text, color }) {
return (
<span style={{ background: color, color: "#fff", padding: "2px 8px", borderRadius: 999, fontSize: 12 }}>
{text}
</span>
);
}
function App() {
return (
<p style={{ display: "flex", gap: 8 }}>
<Badge text="New" color="#2e7d32" />
<Badge text="Sale" color="#c62828" />
<Badge text="Limited" color="#1565c0" />
</p>
);
}