DEV Community

Cover image for RSC and the Echo of 'Presentational and Container Components'
Lorenzo Rivosecchi
Lorenzo Rivosecchi

Posted on

RSC and the Echo of 'Presentational and Container Components'

Introduction

In 2015 Dan Abramov published an article titled Presentational and Container Components.

In the article he suggested a pattern for creating reusable React application by grouping components in two categories.


Presentational (or Dumb) Components

These components have the sole responsibility of rendering UI. Data, Behavior and Logic should be injected by props.

// classes/src/user-profile/presentational.jsx

export default function UserProfile({ user, handleLike }) {
  return (
    <section>
      <h3>
        {user.firstName} {user.lastName}
      </h3>
      <p>{user.bio}</p>
      <button onClick={handleLike}>Like</button>
      <span>{user.likes}</span>
    </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

Container (or Smart) Components

These components are the polar opposite. They should handle Data, Behavior and Logic, and delegate rendering to other components.

// classes/src/user-profile/container.jsx

import { Component } from "react";
import { getUser, likeUser } from "../../../shared/user";
import Presentational from "./presentational";

export default class UserProfile extends Component {
  state = { user: null };
  constructor() {
    super();
    this.handleLike = this.handleLike.bind(this);
    this.revalidate = this.revalidate.bind(this);
  }
  componentDidMount() {
    this.revalidate();
  }
  async revalidate() {
    const user = await getUser();
    if (!user) return;
    this.setState({ user });
  }
  async handleLike() {
    const user = this.state.user;
    if (!user) return;
    await likeUser(user.id);
    this.revalidate();
  }
  render() {
    const user = this.state.user;
    if (!user) return <div>Loading...</div>;
    return <Presentational user={user} handleLike={this.handleLike} />;
  }
}

Enter fullscreen mode Exit fullscreen mode

React Hooks

As mentioned in the original article, since the introduction of Hooks (2018) this pattern is not recommended anymore.
That's because hooks are the chosen primitive for sharing Data, Behavior and Logic between components.

In the hooks version we inject data and behavior by calling special functions that start with use in the component body.

// hooks/src/user-profile/component.jsx
import { useUserProfile } from "./hooks";

export default function UserProfile() {
  const { user, handleLike } = useUserProfile();
  if (!user) return <div>Loading...</div>;
  return (
    <section>
      <h3>
        {user.firstName} {user.lastName}
      </h3>
      <p>{user.bio}</p>
      <button onClick={handleLike}>Like</button>
      <span>{user.likes}</span>
    </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here is how the hooks are defined:

// hooks/src/user-profile/hooks.jsx

import { getUser, likeUser } from "../../../shared/user";
import { useState, useCallback, useEffect } from "react";

export function useUser() {
  const [user, setUser] = useState();

  const revalidate = useCallback(() => {
    getUser().then(setUser);
  }, []);

  useEffect(revalidate, [revalidate]);

  return { user, revalidate };
}

export function useLikeUser() {
  return useCallback(likeUser, []);
}

export function useUserProfile() {
  const { user, revalidate } = useUser();
  const likeUser = useLikeUser();

  const handleLike = useCallback(async () => {
    if (!user) return;
    await likeUser(user.id);
    revalidate();
  }, [user]);

  return {
    user,
    handleLike,
  };
}
Enter fullscreen mode Exit fullscreen mode

React Server Components

Fast forward to 2023, we are all learning React Server Components: A new paradigm that brings the component model to the Server.

With Server components we can create async components that are rendered only on the server. One of the main benefits of this approach is that developers can fetch data at the component level without worrying about creating expensive network waterfalls. The goal of the React team is to make it easier to consume data in our applications without reducing performance.

When i was experimenting with Server Components i found myself doing Presentational and Container components once again.

The server component handles data fetching and mutations using async/await and Server Actions

// rsc/app/page.jsx

import { getUser, likeUser } from "../../shared/user";
import Presentational from "./Presentational";

// In RSC revalidation is handled by the Framework (Next.js)
import { revalidatePath } from "next/cache";

export default async function UserProfile() {
  const user = await getUser();

  async function handleLike() {
    "use server";
    await likeUser(user.id);
    revalidatePath("/");
  }

  return <Presentational user={user} handleLike={handleLike} />;
}
Enter fullscreen mode Exit fullscreen mode

The client component, receives props and renders markup.

"use client";

// rsc/app/Presentational.jsx

import { useTransition } from "react";

export default function UserProfile({ user, handleLike }) {
  const [isPending, startTransition] = useTransition();
  return (
    <section>
      <h3>
        {user.firstName} {user.lastName}
      </h3>
      <p>{user.bio}</p>
      <button
        onClick={() => {
          startTransition(handleLike);
        }}
      >
        Like
      </button>
      <span>{user.likes}</span>
    </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusions

I haven't used this approach in a real application, therefore i'm not sure that this pattern has legs.
In fact, rendering markup only on client components defeats one of the most important benefits of RSC: Shipping less JavaScript to the browser.

I decided to write this article because this case reminded me of how in Software Engineering ideas circulate and change in small but powerful ways. 🌻


Github Repository for this article.

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more