RSC and the Echo of 'Presentational and Container Components'
Tags: react, nextjs, frontend
Published: 2023-06-29 15:14:26

## Introduction

In 2015 [Dan Abramov](https://overreacted.io/) published an article titled [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0).

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.

```tsx
// 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>
  );
}
```


### Container (or Smart) Components

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


```tsx
// 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} />;
  }
}

```

---

## React Hooks

As mentioned in the original article, since the introduction of [Hooks](https://react.dev/learn/reusing-logic-with-custom-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.

```tsx
// 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>
  );
}
```

Here is how the hooks are defined:

```tsx
// 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,
  };
}
```

---

## React Server Components

Fast forward to 2023, we are _all_ learning [React Server Components](https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#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](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions)

```tsx
// 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} />;
}
```

The client component, receives props and renders markup.

```tsx
"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>
  );
}
```

---

## 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](https://github.com/fibonacid/rsc-and-the-echo-of-presentational-and-container-components/tree/main) for this article.