React Component Patterns
In React, there are different patterns for structuring and managing data in components. Most of the examples in this document are based on the same visual component: a PropertyCard.
1. Basic Prop Patterns
These are the most common methods for passing data to a component.
Spread Props
This involves passing a complete props object to a component using the spread syntax ({...props}).
// The component receives all props and passes them directly
<PropertyCard {...props} />
Although it is a very concise way of writing, it has a significant disadvantage: lack of clarity. When reading the code, it is not evident which specific properties the PropertyCard component is expecting or using. To know this, it is necessary to inspect the component's type definition, which reduces readability.
Use Case: Useful for simple components that act as "wrappers," whose sole purpose is to pass properties to a child component without modifying them. In general, for more complex components, a more explicit method is preferable.
Manual Props
This is the opposite method. Each property is passed explicitly and individually to the component.
// Each prop is declared manually
<PropertyCard
title={props.title}
location={props.location}
price={props.price}
// ...and so on for the rest
/>
This approach is much more "verbose" (requires writing more code), but its great advantage is clarity and explicitness. Anyone reading the code can immediately see what data is being passed to the component, which facilitates debugging and maintenance.
Use Case: This is the most recommended pattern for most cases due to its readability. It is ideal in medium and large applications, or when working in a team.
Value Prop (Data Object)
Instead of passing multiple individual props, all related data is grouped into a single object, which is passed through a single prop, commonly named value or data.
// All property data is in a single object
const propertyData = { title: "...", location: "...", ... };
<PropertyCard value={propertyData} />
This pattern helps organize and separate the responsibilities of the props. It clearly separates the model data (what is to be displayed) from other behavioral or configuration props (e.g., onClick, variant, isDisabled).
Use Case: Excellent when a component represents a specific data entity (a user, a product, a property). It allows for maintaining a clean and easy-to-understand API.
2. Children as an Array Pattern (Child Manipulation)
This pattern consists of a container component that receives multiple child components (children) and applies logic or styles to them individually, often based on their position in the list.
const FadeInGrid = ({ children }) => {
// 1. Convert 'children' into an array
const childrenArray = React.Children.toArray(children);
return (
<div className="fade-in-grid-container">
{/* 2. Map over the array of children */}
{childrenArray.map((child, index) => (
// 3. Wrap each child and apply logic based on its index
<div
key={index}
className="fade-in-item"
style={{ animationDelay: `${index * 0.2}s` }} // 0.2s delay for each element
>
{child}
</div>
))}
</div>
);
};
As an example, we have a FadeInGrid component. This component takes its children, converts them into an array (React.Children.toArray), and then iterates over them. It wraps each child in a div with an animation delay (animation-delay) that increases according to its index. This creates a staggered appearance effect.
Use Case: Ideal for applying effects to a list of items, such as staggered animations, adding separators between elements, or any logic that needs to treat children as an ordered collection.
3. Compound Components Pattern
This is one of the most powerful and functional patterns. Libraries like shadcn/ui use it as a reference for creating components.
Presentational (Layout Control)
The main component is broken down into sub-components that are exposed as properties of the parent component (e.g., PropertyCard.Title, PropertyCard.Gallery). The developer can then compose the UI with complete flexibility.
<PropertyCard.Card>
<PropertyCard.Gallery images={props.images} />
<PropertyCard.Content>
{/* The order can be easily changed */}
<PropertyCard.Location location={props.location} />
<PropertyCard.Title title={props.title} />
</PropertyCard.Content>
</PropertyCard.Card>
It offers maximum flexibility. You can decide which sub-components to render and in what order. This allows for creating multiple variations of the same base component without needing boolean props like showGallery or hidePrice.
Managed (with Context)
This is an evolution of the previous pattern to avoid "prop drilling" (passing props through multiple levels).
A root component (PropertyCard.Root) receives the main data and uses React's Context API to make it available to all its descendants. The sub-components (like Title, Location, etc.) consume this context to get the data they need, without it having to be passed to them directly.
The code becomes much cleaner, as the data is provided only once in the root component. This centralizes state management and simplifies the component's API. The main disadvantage is that, by using Context, these components are not compatible with React Server Components (RSC) and must be marked as Client Components ('use client').
Use Case: Extremely useful for creating complex and flexible component libraries like dialogs, selects, accordions, etc., where multiple parts need to share state or data.
4. Container & Presentational Pattern
This is a classic pattern that separates logic from presentation. This pattern comes from the Redux era.
- Container Component: Handles the logic: fetching data, managing state, etc. It has no styles.
- Presentational Component: Handles the UI: how things look. It receives data through props and is "dumb" (it doesn't know where the data comes from).
The PropertyCardContainer receives an id, simulates data loading, and during that time shows a loader. Once it has the data, it renders the PropertyCard (presentational) component, passing it the information.
Use Case: Very useful for decoupling business logic from the UI. It facilitates testing (you can test the presentational component in Storybook with mock data) and component reuse.
5. Controlled & Uncontrolled Pattern
This pattern defines who is responsible for managing a component's state, especially in forms.
Explanation:
- Controlled: The state is managed by the parent component. The parent passes the current
valueand a function to update it (onChange) to the child component. The "source of truth" resides in the parent. - Uncontrolled: The component manages its own state internally (using
useState). From the outside, it is simpler to use because you don't have to manage its state, but you lose control over it.
An example is the Tabs component. In the controlled version, the parent decides which tab is active. In the uncontrolled version, the Tabs component itself decides which tab is active.
Use Case: Use Controlled when the component's state needs to be read or modified by the parent (e.g., form validation). Use Uncontrolled for simple, self-contained components whose state does not affect the rest of the application (e.g., a simple visual switch).
6. Render Props Pattern
A technique for sharing logic and state. A component encapsulates logic (managing a search state) and instead of rendering something fixed, it exposes that state to its children through a prop that is a function (children).
<WithSearch>
{/* 'children' is a function that receives the 'query' state */}
{(query) => (
<div>
{/* 'query' is used here to filter and render */}
</div>
)}
</WithSearch>
The WithSearch component handles the logic of the search input and its state. The parent component passes it a function that receives the search value (query) and is responsible for deciding what to render with that value.
Use Case: It allows for inverting control, where the child (the component providing the logic) gives data to the parent (which decides how to use it for rendering). It is a way to decouple logic from rendering. Nowadays, many of these functionalities are solved more simply with custom hooks.