reacttypescriptpatternsfrontendclean-code
Writing Clean React Components: Patterns I Actually Use
Practical patterns for writing maintainable React code, learned from real projects.
December 12, 20254 min read
The Problem
Most React tutorials show you the basics. Few show you how to write code that doesn't become a nightmare to maintain.
Here are patterns I actually use in production.
1. Compound Components
Instead of prop drilling:
tsx
// ❌ Prop drilling nightmare
<Card
title="Project"
description="..."
footer={<Button>View</Button>}
headerAction={<IconButton />}
/>
// ✅ Compound components
<Card>
<Card.Header>
<Card.Title>Project</Card.Title>
<Card.Action><IconButton /></Card.Action>
</Card.Header>
<Card.Body>...</Card.Body>
<Card.Footer><Button>View</Button></Card.Footer>
</Card>2. Custom Hooks for Logic
Extract business logic into hooks:
tsx
function useProjects() {
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchProjects().then(setProjects).finally(() => setLoading(false));
}, []);
return { projects, loading };
}
// Component stays clean
function ProjectList() {
const { projects, loading } = useProjects();
if (loading) return <Skeleton />;
return projects.map(p => <ProjectCard key={p.id} {...p} />);
}3. Render Props for Flexibility
When you need maximum flexibility:
tsx
<DataFetcher url="/api/projects">
{({ data, loading, error }) => (
loading ? <Spinner /> :
error ? <Error message={error} /> :
<ProjectGrid projects={data} />
)}
</DataFetcher>4. Error Boundaries
Always wrap risky components:
tsx
<ErrorBoundary fallback={<ErrorCard />}>
<RiskyComponent />
</ErrorBoundary>Key Takeaways
- Composition > Configuration: Prefer composable APIs over prop-heavy components
- Hooks for logic: Keep components focused on rendering
- Fail gracefully: Always have error boundaries
These patterns have saved me countless hours of debugging.