Table of Contents
Component Composition in React
Component composition is a powerful pattern in React that allows you to build complex UIs by combining simpler components.
Children Prop Pattern
// Card component that accepts children
function Card({ children, className = '' }) {
return (
{children}
);
}
// Usage
function App() {
return (
John Doe
Software Developer
);
}
Render Props Pattern
// DataFetcher component using render props
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return render({ data, loading, error });
}
// Usage
function UserList() {
return (
{
if (loading) return Loading...;
if (error) return Error: {error.message};
return (
{data.map(user => (
- {user.name}
))}
);
}}
/>
);
}
Higher-Order Components (HOCs)
// HOC for adding loading state
function withLoading(WrappedComponent) {
return function WithLoadingComponent(props) {
if (props.loading) {
return Loading...;
}
return ;
};
}
// Usage
const UserListWithLoading = withLoading(UserList);
function App() {
const [loading, setLoading] = useState(true);
const [users, setUsers] = useState([]);
return (
);
}
Compound Components Pattern
// Modal compound component
const Modal = ({ children, isOpen, onClose }) => {
if (!isOpen) return null;
return (
e.stopPropagation()}>
{children}
);
};
Modal.Header = ({ children }) => (
{children}
);
Modal.Body = ({ children }) => (
{children}
);
Modal.Footer = ({ children }) => (
{children}
);
// Usage
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
setIsOpen(false)}>
Confirm Action
Are you sure you want to delete this item?
>
);
}
Controlled vs Uncontrolled Components
Controlled Component
function ControlledInput() {
const [value, setValue] = useState('');
return (
setValue(e.target.value)}
placeholder="Controlled input"
/>
);
}
Uncontrolled Component
function UncontrolledInput() {
const inputRef = useRef();
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<>
>
);
}
Flexible Component API
// Button component with flexible API
function Button({
children,
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
leftIcon,
rightIcon,
onClick,
...props
}) {
const baseClasses = 'btn';
const variantClasses = `btn-${variant}`;
const sizeClasses = `btn-${size}`;
const disabledClasses = disabled ? 'btn-disabled' : '';
const loadingClasses = loading ? 'btn-loading' : '';
const className = [
baseClasses,
variantClasses,
sizeClasses,
disabledClasses,
loadingClasses
].filter(Boolean).join(' ');
return (
);
}
// Usage examples
}>Save
Practical Exercise
Create a flexible Table component that supports:
- Compound component pattern (Table.Header, Table.Body, Table.Row, Table.Cell)
- Sorting functionality
- Loading and empty states
- Customizable styling
Best Practices
- Prefer composition over inheritance
- Use children prop for flexible content
- Create reusable, configurable components
- Keep component APIs simple and intuitive
- Use TypeScript for better component contracts