Explain React design patterns.
Design patterns in React are reusable solutions to common problems that arise during application development. They help create more maintainable, scalable, and understandable codebases by promoting best practices and architectural consistency. Understanding these patterns enables developers to build robust and efficient React applications.
Container/Presentational Components
This pattern separates concerns by dividing components into two categories: Presentational (or 'Dumb') components focus solely on how things look, receiving data and callbacks via props. Container (or 'Smart') components focus on how things work, managing state, data fetching, and passing data down to presentational components. This separation improves reusability, testability, and clarity of purpose for each component.
Higher-Order Components (HOCs)
HOCs are functions that take a component as an argument and return a new component with enhanced capabilities. They are a powerful way to reuse component logic, such as data fetching, state management, or styling, across multiple components without duplicating code. HOCs are a form of decorator pattern for components, typically used before the advent of Hooks.
const withLogger = (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
console.log(`${WrappedComponent.name} did mount.`);
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
const MyComponent = (props) => <div>Hello {props.name}</div>;
const LoggedMyComponent = withLogger(MyComponent);
Render Props
The render props pattern involves a component that takes a function as a prop, which it calls to determine what to render. This allows components to share code and behavior (like state or data) without explicitly extending classes or using HOCs. The child component can receive the shared state or logic as arguments to the render function, enabling flexible UI rendering based on that logic.
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
const App = () => (
<MouseTracker render={({ x, y }) => (
<h1>The mouse position is ({x}, {y})</h1>
)} />
);
Custom Hooks
Introduced with React Hooks, custom hooks are functions that allow you to reuse stateful logic across multiple functional components. They encapsulate behavior and state management, providing a cleaner and more direct way to share logic compared to HOCs or render props, especially in modern React applications. A custom hook is simply a JavaScript function whose name starts with 'use' and that may call other Hooks.
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
function MyComponent() {
const width = useWindowWidth();
return <div>Window width: {width}px</div>;
}
Context API (Provider Pattern)
The React Context API provides a way to pass data through the component tree without having to pass props down manually at every level. It's often used for global data like user authentication, themes, or locale settings. The Provider component makes data available to all consuming components nested below it, regardless of how deep they are. This implements a form of the Provider pattern, simplifying state management for widely used data.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemeProvider({ children }) {
const theme = 'dark'; // Example theme
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button style={{ background: theme === 'dark' ? 'black' : 'white', color: theme === 'dark' ? 'white' : 'black' }}>Styled Button</button>;
}
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}
Compound Components
This pattern allows you to build components that work together to share implicit state and functionality, but give the user full control over the rendering. Examples include <Select> and <Option>, <Tabs> and <Tab>, or <Form> and <Input>. They leverage React.Children and React.cloneElement (or Context API in modern React) to pass props and context implicitly among their children, enabling flexible and semantic UI structures.