In React applications, components rarely work in isolation. They often need to communicate with each other to share data, trigger actions, or respond to user interactions. Component communication patterns define how this data flow is structured and maintained.
Component Communication Patterns
The most common communication pattern in React is parent-to-child communication. In this pattern, data is passed from a parent component to a child component using props. This is the default and most straightforward way to share data.
Example:
function Child({ message }) {
return <p>{message}</p>;
}
function Parent() {
return <Child message="Hello from Parent" />;
}
Another important pattern is child-to-parent communication. Since React follows a unidirectional data flow, children cannot directly change parent state. Instead, the parent passes a function to the child, and the child calls that function when an event occurs.
Example:
function Child({ onSend }) {
return <button onClick={() => onSend("Data from Child")}>Send</button>;
}
function Parent() {
function handleData(data) {
console.log(data);
}
return <Child onSend={handleData} />;
}
Sibling communication occurs when two components need to share data but are not directly related. This is handled by lifting the shared state to their closest common parent and passing data down as props.
Example:
function Input({ onChange }) {
return <input onChange={e => onChange(e.target.value)} />;
}
function Display({ value }) {
return <p>{value}</p>;
}
function Parent() {
const [text, setText] = React.useState("");
return (
<>
<Input onChange={setText} />
<Display value={text} />
</>
);
}
These patterns ensure predictable data flow and clear ownership of state.
Presentational vs Container Components
Presentational and container components represent a design pattern used to separate concerns in React applications.
Presentational components focus purely on how the UI looks. They receive data via props and render UI elements. They do not manage state or contain business logic.
Example of a presentational component:
function UserCard({ name, email }) {
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
This component is reusable, easy to test, and unaware of where the data comes from.
Container components focus on how things work. They manage state, fetch data, handle events, and pass data down to presentational components.
Example of a container component:
function UserContainer() {
const [user, setUser] = React.useState({
name: "Akbar",
email: "akbar@email.com"
});
return <UserCard name={user.name} email={user.email} />;
}
Here, the container handles the data and logic, while the presentational component handles the UI.
Real-World Scenario
In a real-world application such as a dashboard or form-based system, container components handle API calls, validation, and state updates, while presentational components render forms, tables, or cards. This separation improves readability, testing, and maintainability, especially in large codebases.
Important Notes and Best Practices
Not every project requires strict separation into presentational and container components, especially in small applications. However, this pattern becomes valuable as applications grow. Modern React with Hooks often combines logic and UI in functional components, but the conceptual separation is still useful for structuring code.
In summary, component communication patterns define how data flows between components, while presentational and container components help separate UI from logic. Together, these concepts form the foundation for building clean, scalable, and maintainable React applications.