ReactJs Components
Components are the fundamental building blocks of React applications. They encapsulate UI elements and their associated logic, enabling developers to build complex interfaces by composing smaller, reusable pieces. React components can be thought of as JavaScript functions that accept inputs (called “props”) and return React elements describing what should appear on the screen.
Component Types
React supports two primary types of components: function components and class components. Since React 16.8, function components with Hooks have become the recommended approach for new development, offering a more straightforward API and better code reusability.
Function components are JavaScript functions that return JSX:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Class components provide the same functionality but use ES6 class syntax:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
The Key Attribute in Lists
When rendering lists of elements, React requires a special key prop to efficiently track which items have changed, been added, or removed. The key helps React identify individual elements in the Virtual DOM and optimize re-renders.
Why Keys Matter
Keys serve as stable identifiers for list items. Without keys, React must compare every element in a list during reconciliation, which can lead to performance issues and incorrect component state. Consider this example:
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
Key Selection Strategies
Choosing appropriate keys is essential for optimal performance and correctness:
-
Data-based keys (recommended) - Use unique identifiers from your data:
{items.map(item => <Item key={item.id} {...item} />)} -
Index as key (use with caution) - Only acceptable for static lists that never reorder:
{items.map((item, index) => <Item key={index} {...item} />)}Using index as a key has drawbacks when items can be reordered, inserted, or deleted. For example, using Array.reverse() on a list with index keys will not trigger proper re-renders.
- Avoid Math.random() - Never use random values as keys, as they change on every render and defeat the purpose of keys entirely.
Controlled vs Uncontrolled Components
React provides two patterns for managing form inputs and other stateful elements: controlled and uncontrolled components.
Controlled Components
In controlled components, React manages the form state. The component’s state becomes the “single source of truth” for the input’s value:
function ControlledForm() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<input
type="text"
value={value}
onChange={handleChange}
/>
);
}
This pattern provides full control over the input’s behavior and makes it easy to validate, format, or transform user input in real-time.
Uncontrolled Components
Uncontrolled components rely on the DOM to maintain their state, similar to traditional HTML forms:
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
console.log(inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
}
The official documentation provides detailed guidance on when to use each pattern. Controlled components are generally preferred when you need to validate input, conditionally disable buttons, or enforce specific formats.
Component Composition Patterns
React’s component model encourages composition over inheritance. Several patterns help you build flexible, reusable components.
Containment
Some components don’t know their children ahead of time. The children prop allows components to accept and render arbitrary child elements:
function Dialog({ children, title }) {
return (
<div className="dialog">
<h2>{title}</h2>
<div className="dialog-content">
{children}
</div>
</div>
);
}
function WelcomeDialog() {
return (
<Dialog title="Welcome">
<p>Thank you for visiting!</p>
</Dialog>
);
}
Specialization
Specialization involves creating specific instances of more generic components:
function Button({ children, variant = 'primary', ...props }) {
return (
<button className={`btn btn-${variant}`} {...props}>
{children}
</button>
);
}
function PrimaryButton(props) {
return <Button variant="primary" {...props} />;
}
function DangerButton(props) {
return <Button variant="danger" {...props} />;
}
Compound Components
Compound components work together as a cohesive unit, sharing implicit state. This pattern creates flexible APIs with better encapsulation. Kent C. Dodds provides an excellent deep dive in React Hooks: Compound Components.
Advanced Component Patterns
Higher-Order Components (HoC)
Higher-Order Components are functions that take a component and return a new component with enhanced functionality:
function withMousePosition(Component) {
return function EnhancedComponent(props) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return <Component {...props} mousePosition={position} />;
};
}
The with prefix is a naming convention for HoCs (e.g., withAuth, withRouter, withTheme). HoCs should enhance components without mutating them, following the principle of composition over mutation.
Render Props
The render props pattern uses a prop whose value is a function to determine what to render:
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return render(position);
}
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<p>Mouse position: {x}, {y}</p>
)}
/>
);
}
While powerful, render props have largely been replaced by custom Hooks in modern React applications, which offer similar functionality with cleaner syntax.
Contexts for Cross-Component Communication
When data needs to be accessible by many components at different nesting levels, prop drilling becomes cumbersome. The Context API provides a way to share values between components without explicitly passing props through every level of the tree. See the dedicated contexts page for detailed information.
Resources
- React Official Documentation - Components and Props
- Thinking in React - Official guide on component-based thinking
- Kent C. Dodds - Compound Components
- React Patterns - Community collection of React patterns