React Architecture - Presentational and Container Components

May 13, 2023

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.

Are you looking to take the next step as a developer? Whether you're a new developer looking to break into the tech industry, or just looking to move up in terms of seniority, book a coaching session with me and I will help you achieve your goals.