In React, we typically separate components into two categories: presentational components and container components. Presentational components are concerned with the visual representation of data and user interface, while container components handle data management and business logic. This separation helps us create more modular and reusable code, making our applications more scalable and easier to maintain.
Presentational components are simple, stateless, and focus on rendering data that is passed down to them as props. They can be as simple as a button or as complex as a complete user interface component. Here's an example of a simple presentational component:
function Button(props) {
return <button onClick={props.onClick}>{props.label}</button>;
}
This is a simple button component that receives a label
and an onClick
function as props and renders a button with the label as the text. When the button is clicked, it calls the onClick
function passed down as a prop. This component doesn't have any state or business logic, and its only concern is how it looks.
Container components, on the other hand, are responsible for data management and business logic. They handle state changes, communicate with APIs, and pass data down to presentational components as props. Here's an example of a container component that makes an API call to fetch data and passes it down to a presentational component:
class UserListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(data => this.setState({ users: data }));
}
render() {
return <UserList users={this.state.users} />;
}
}
In this example, we have a container component that manages state for a list of users. It fetches data from the https://jsonplaceholder.typicode.com/users
API endpoint in the componentDidMount
lifecycle method and sets the state of the users
array. The render
method then passes this state down to a presentational component, UserList
, as a prop.
The UserList
component is a presentational component that receives an array of users as a prop and renders them in an unordered list:
function UserList(props) {
return (
<ul>
{props.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
In this example, we've separated the data management and rendering concerns into separate components. The UserListContainer
component handles data fetching and state management, while the UserList
component only concerns itself with rendering the data passed down to it.
Another common use case for container components is handling user input and form submission. Here's an example of a container component that handles form input and submits data to an API:
class AddUserContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
email: "",
};
}
handleNameChange = event => {
this.setState({ name: event.target.value });
};
handleEmailChange = event => {
this.setState({ email: event.target.value });
};
handleSubmit = event => {
event.preventDefault();
const { name, email } = this.state;
fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
body: JSON.stringify({ name, email }),
})
.then(response => response.json())
.then(data => console.log("Success:", data))
.catch(error => console.error("Error:", error));
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
type="text"
value={this.state.name}
onChange={this.handleNameChange}
/>
</label>
<label>
Email:
<input
type="text"
value={this.state.email}
onChange={this.handleEmailChange}
/>
</label>
<button type="submit">Add User</button>
</form>
);
}
}
In this example, we have a container component that manages state for a form with two input fields: name
and email
. The handleNameChange
and handleEmailChange
methods update the component's state when the corresponding input field changes.
When the form is submitted, the handleSubmit
method sends a POST request to the https://jsonplaceholder.typicode.com/users
endpoint with the data from the form as the request body. If the request is successful, the data returned from the server is logged to the console.
This is another example of a container component that handles data management and business logic, while a presentational component, in this case, the form, only concerns itself with rendering the data passed down to it.
In conclusion, separating components into presentational and container components can help us create more modular and reusable code in React. Presentational components focus on rendering data, while container components handle data management and business logic. We've seen examples of container components that fetch data from APIs and handle form submission with POST requests, while passing data down to presentational components. This separation of concerns can make our code more scalable, maintainable, and easier to test.