React is powerful because it gives us flexibility in how we design and structure components. As projects grow, you’ll find yourself needing different patterns to organize code, share logic, and create reusable UIs.
In this blog, we’ll cover 6 important React component patterns with definitions, explanations, and examples:
1. Controlled Components
2. Uncontrolled Components
3. Container vs Presentational Components
4. Higher-Order Components (HOCs)
5. Render Props
6. Compound Components
---
🔹 1. Controlled Components
Definition:
A controlled component is a form element (like <input>, <textarea>, <select>) where the value is controlled by React state. The UI reflects the state, and every change updates the state explicitly.
This ensures one source of truth for form values.
Example – Controlled Input
import React, { useState } from "react";
function ControlledForm() {
// State holds the input value
const [name, setName] = useState("");
// Update state on input change
const handleChange = (e) => setName(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
alert(`Submitted name: ${name}`);
};
return (
<form onSubmit={handleSubmit}>
{/* Controlled input: value comes from state */}
<input type="text" value={name} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
);
}
export default ControlledForm;
📌 Here, value={name} makes the input controlled by state.
---
🔹 2. Uncontrolled Components
Definition:
An uncontrolled component lets the DOM itself handle form data. Instead of storing the value in React state, we use a ref to directly access the DOM value when needed.
This is useful for quick forms or integrating with non-React code.
Example – Uncontrolled Input
import React, { useRef } from "react";
function UncontrolledForm() {
// Ref points to the DOM element
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// Directly read value from DOM
alert(`Submitted name: ${inputRef.current.value}`);
};
return (
<form onSubmit={handleSubmit}>
{/* Uncontrolled input: no state */}
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
}
export default UncontrolledForm;
📌 This avoids state updates, but reduces React’s control.
---
🔹 3. Container vs Presentational Components
Definition:
This pattern separates logic from UI.
Presentational components → Focus only on how things look (UI).
Container components → Handle how things work (state, data fetching, logic).
Example – Separation of Concerns
// Presentational (UI only)
function UserList({ users }) {
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
// Container (fetching + state)
function UserContainer() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
// Fake API call
setTimeout(() => {
setUsers([{ id: 1, name: "Tushar" }, { id: 2, name: "Sneha" }]);
}, 1000);
}, []);
return <UserList users={users} />;
}
export default UserContainer;
📌 Benefits: cleaner, reusable UI and easier testing.
---
🔹 4. Higher-Order Components (HOCs)
Definition:
A Higher-Order Component (HOC) is a function that takes a component and returns a new component, adding extra logic or behavior.
It’s commonly used for cross-cutting concerns like logging, authentication, theming, etc.
Example – WithLogger HOC
import React from "react";
// HOC adds logging behavior
function withLogger(WrappedComponent) {
return function EnhancedComponent(props) {
console.log("Props received:", props);
return <WrappedComponent {...props} />;
};
}
// Base component
function Greeting({ name }) {
return <h2>Hello, {name}</h2>;
}
// Enhanced component
const GreetingWithLogger = withLogger(Greeting);
export default function App() {
return <GreetingWithLogger name="Tushar" />;
}
📌 Here, withLogger wraps Greeting to add logging without modifying the original component.
---
🔹 5. Render Props
Definition:
The Render Props pattern means passing a function as a prop that returns React elements. This lets us share logic between components while keeping UI flexible.
Example – Mouse Tracker
import React, { useState } from "react";
// Logic provider with render prop
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div
style={{ height: "200px", border: "1px solid black" }}
onMouseMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}
>
{render(position)} {/* UI decided by parent */}
</div>
);
}
// Usage
function App() {
return (
<MouseTracker
render={({ x, y }) => <p>Mouse position: {x}, {y}</p>}
/>
);
}
export default App;
📌 The parent decides what to render, while MouseTracker just provides data.
---
🔹 6. Compound Components
Definition:
Compound components allow you to create a parent component with child components that work together, providing a flexible API for building complex UIs (like tabs, modals, dropdowns).
Example – Tabs System
import React, { useState } from "react";
// Parent component
function Tabs({ children }) {
const [active, setActive] = useState(0);
// Clone children and inject props
return (
<div>
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
isActive: index === active,
onClick: () => setActive(index),
})
)}
</div>
);
}
// Child Tab component
function Tab({ label, isActive, onClick }) {
return (
<button
style={{
fontWeight: isActive ? "bold" : "normal",
marginRight: "10px",
}}
onClick={onClick}
>
{label}
</button>
);
}
// Example usage
function App() {
return (
<Tabs>
<Tab label="Home" />
<Tab label="Profile" />
<Tab label="Settings" />
</Tabs>
);
}
export default App;
📌 This gives users a flexible API where they can simply write <Tabs><Tab ... /></Tabs> without worrying about internal logic.
---
🔑 Key Takeaways
Controlled Components → React manages form state.
Uncontrolled Components → DOM manages form state (via refs).
Container vs Presentational → Separate logic from UI.
HOCs → Wrap components with extra behavior.
Render Props → Share logic via functions as children.
Compound Components → Build flexible, reusable APIs.
Comments
Post a Comment