React & Next.js
Writing React components that stay reusable
Practical rules for components that survive their second and third use: intent-based props, composition with children, and forwarding native props.
"Reusable component" is easy to say and surprisingly easy to get wrong. A component that is reusable in theory often grows a new boolean prop every time someone uses it, until nobody can tell what it renders. These are the rules I follow to avoid that.
Extract on the second use, not the first
The first time you build something, you don’t yet know which parts will vary. Build it inline. When the same thing appears a second time, you can see exactly what differs between the two — and those differences become the props.
Props should describe intent, not styles
A prop like color="#9270ff" leaks styling decisions into every caller. A prop like variant="primary" describes what the button is for, and the component decides what that looks like.
type ButtonProps = {
variant?: 'primary' | 'secondary' | 'ghost'
size?: 'sm' | 'md' | 'lg'
}When the brand colour changes, you edit one file instead of searching the codebase.
Forward the native props
A Button should accept everything a <button> accepts — disabled, type, aria-*, onClick — without you listing each one. Extend the native element’s props and spread the rest.
import type { ComponentProps } from 'react'
type Props = ComponentProps<'button'> & {
variant?: 'primary' | 'secondary'
}
export function Button({ variant = 'primary', className, ...rest }: Props) {
return <button className={cx(styles[variant], className)} {...rest} />
}Callers can now pass aria-label or form the day they need it, and the component never has to change.
Prefer composition over configuration
When a component starts collecting props like showIcon, iconPosition, title, subtitle and footerText, it usually wants children instead. Let callers pass in the pieces they need.
// Configuration: every new layout needs a new prop
<Card title="Plan" subtitle="Monthly" showBadge badgeText="Popular" />
// Composition: the card provides the frame, callers provide the content
<Card>
<Badge>Popular</Badge>
<h3>Plan</h3>
<p>Monthly</p>
</Card>Keep data fetching out of UI components
A ProductCard that fetches its own product can only ever show data from that one endpoint. A ProductCard that receives a product prop can be used in a list, a search result, a preview in a CMS and a test. Fetch in the page or a container component, and pass plain data down.
Build accessibility in once
The best part of a shared component is that you only have to get the hard parts right once. Focus styles, aria-expanded on toggles, label-to-input wiring, keyboard handling for menus — put them in the component, and every screen that uses it gets them for free.
- Extract when you see the second use.
- Name props after intent.
- Accept and forward the native element’s props.
- Reach for
childrenbefore adding another config prop. - Pass data in; don’t fetch inside.
- Make accessibility part of the component, not the page.
Related articles
- React & Next.js, Frontend
Server or Client Component? A practical guide for the Next.js App Router
When a component needs "use client", how to keep the client boundary small, and the children pattern that lets server content live inside client wrappers.
Read article
Contact
Have a project or a role in mind? Let’s talk.
Send a message through the contact form, or email me directly.