The pattern of Container and Presenter components in React is an effective method for structuring React applications. This approach not only assists in keeping the code-base organized but also in simplifying the development workflow.
Containers:
- State Management: State management involves container components taking on the responsibility of managing and controlling the state. These components deal with intricate logic that might include engaging with Redux stores, local state, or the Context API.
- Data Handling: Containers frequently manage tasks such as retrieving data, manipulating data, and managing errors. They may communicate with APIs or services, analyze data, and format it so that the display components can present it easily.
- Business Logic: The business logic within these components involves making decisions, calculating values, and handling user interactions, which allows it to operate separately from the user interface.
We will showcase the pattern of Container and Presentational components by utilizing a BlogPostListContainer and a BlogPostList component in an imaginary blog application.
BlogPostListContainer (Container Component)
The BlogPostListContainer is in charge of retrieving post information and handling state. It contains the logic and provides the required data to the presentational component.
Assuming we are using an Apollo client to fetch the data.
// apolloClient.ts
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
const httpLink = new HttpLink({
uri: "your-graphql-endpoint",
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
export default client;
Using the Apollo Provider
To make Apollo Client available in your React component tree, you need to wrap your application with ApolloProvider in the root component (usually in App.tsx).
import React from "react";
import { ApolloProvider } from "@apollo/client";
import apolloClient from "./apolloClient";
import BlogPostListContainer from "./blog-post-list-container";
function App() {
return (
<ApolloProvider client={apolloClient}>
<BlogPostListContainer />
</ApolloProvider>
);
}
export default App;
The BlogPostListContainer to use the useQuery hook from Apollo Client.
import React from "react";
import { useQuery, gql } from "@apollo/client";
import BlogPostList from "./blog-post-list";
// Define a type for the post
type Post = {
id: number;
title: string;
description: string;
author: string;
};
// GraphQL query
const GET_POSTS = gql`
query GetPosts {
posts {
id
title
description
author
}
}
`;
function BlogPostListContainer() {
const { loading, error, data } = useQuery<{ posts: Post[] }>(GET_POSTS);
if (loading) return <div>Loading posts...</div>;
if (error) return <div>Error loading posts: {error.message}</div>;
return <BlogPostList posts={data?.posts || []} />;
}
export default BlogPostListContainer;
The BlogPostListContainer example represents the concept of a Container component in React, especially within the context of the Container and Presentational pattern. Below is a brief overview of the characteristics that classify it as a Container:
Data Management and Business Logic
- Fetching Data: Retrieving post data from a GraphQL endpoint is the responsibility of the BlogPostListContainer. Apollo Client’s useQuery hook is used for this purpose. By handling the data fetching logic, the container wraps one of the main elements of business logic in an application.
- State Management: State Management involves managing different states related to data retrievals, like handling loading, error, and data. These states are important for controlling how the user interface reacts to different phases of the data retrieval process, such as displaying a loading indicator during data fetching.
Separation of Concerns
- No UI Rendering Logic: There is no UI Rendering Logic in this component. Its main responsibility is to fetch and manage the data and state, rather than worrying about how the data is displayed in the UI. This component strictly follows the principle that Container components should not be responsible for directly rendering the UI.
- Passing Props to Presentational Components: Passing data to presentational components: The container component sends the retrieved information (in this instance, posts) to a presentational component (BlogPostList). This is an important feature of container components – they provide data and functions as properties to presentational components, which are responsible for displaying the user interface.
Reusability and Modularity
- Modular Structure: The functionality of the BlogPostListContainer is contained within a modular structure, ensuring reusability and maintainability of the component. Any necessary changes to the data fetching or state management logic can be made within this container without impacting the Presentational components.
- Scalability: In a larger application, this method enables scalability by separating the data management logic from the UI components. Managing and updating the application’s logic becomes simpler without having to modify the UI components.
Presentational Components
- Purely UI-focused: These elements are mainly focused on the user interface and how things appear on the screen. It is important to keep them simple and stateless, concentrating solely on the visual aspects.
- Reusable and Composable: Because they are not reliant on the application’s business logic, they can be conveniently utilized in various sections of the application. This ability to be reused is a considerable benefit for growing applications.
- Props Driven: All the necessary data is passed to them through props, making their behavior predictable and simplifying testing. This also includes the callback functions they use, which are typically supplied by container components.
BlogPostList (Presentational Component in TypeScript)
The presentational component BlogPostList in the blog application will receive post data from BlogPostListContainer and will primarily focus on the display of the data. The display could be a list or a grid layout, depending on the design specifications.
We will define a type for the props of the BlogPostList component.
import React from "react";
// Define the props type
type BlogPostListProps = {
posts: {
id: number;
title: string;
description: string;
author: string;
}[];
};
function BlogPostList({ posts }: BlogPostListProps) {
return (
<div>
<h2>Posts</h2>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.description}</p>
<strong>Author: ${post.author}</strong>
</li>
))}
</ul>
</div>
);
}
export default BlogPostList;
The BlogPostList component in the example is an excellent illustration of a Presentational component in React. Its characteristics align with the core principles of the Presentational component pattern. Here’s an in-depth look at its primary features:
Focus on Rendering UI
- Purely for Presentation: BlogPostList focuses solely on the presentation of the post list in the UI. It takes the given data (in this instance, the post list) and displays it. This exclusive emphasis on presentation is in perfect harmony with the function of Presentational components.
- No Business Logic: The BlogPostList component, unlike Container components, is not responsible for fetching data, managing application state, or any other form of business logic. Its sole responsibility is to render the output of the data it receives.
Data Handling through Props
- Props Driven: The component gets all the required data through its props, particularly the array of posts. This is a characteristic of Presentational components – they depend on the data that is sent to them, which helps them stay separate from the logic of sourcing the data.
- Stateless Nature: In this particular instance, the “BlogPostList” does not handle or retain any internal state associated with the application’s data. While it may have internal state for UI reasons (such as toggling a dropdown within the list), it stays separate from the application’s core business logic.
Real-world use cases for this pattern:
User Profile Page:
- Presentational Component: Exhibits user details such as name, photo, and bio. Its primary focus is on the user interface and the presentation of information.
- Container Component: Manages the retrieval of user data from an API, handles state management, and contains the logic for updating or editing the profile.
E-commerce Product List:
- Presentational Component: This is a grid or list of products, where each product card displays an image, title, price, and other details. This component focuses solely on the presentation of the products.
- Container Component: Handles the retrieval of product data from a server, sorts products based on user input, and performs other business logic such as pagination and sorting.
Dashboard Widgets:
- Presentational Component: Single elements on a dashboard, such as a chart, table, or summary card. These elements display data in a particular format.
- Container Component: The Container Component is responsible for retrieving and handling the data required for each widget, such as user analytics or sales figures, and then transferring it to the presentational components.
Forms:
- Presentational Component: A generic form layout with different input fields, labels, and buttons is known as a Presentational Component. It emphasizes the arrangement and appearance of the form elements.
- Container Component: Handles validation, submission, and management of form state as well as any interactions with backend services, this component is responsible for containerization.
Conclusion
Using the Container and Presentational components pattern has allowed us to separate the responsibilities of data fetching and state management from UI rendering. The logic is handled by the BlogPostListContainer, while the BlogPostList focuses solely on displaying the post data. This clear separation enhances the manageability, testability, and reusability of the components.