Exploring the Power of React Server Components and React Suspense
Tags: react, nextjs, servercomponents, reactsuspense
Published: 2023-12-04 10:03:01

React and Next.js have launched many updates this year, and among them, React Server Components and React Suspense have sparked significant discussion in the developer community. If you're not yet familiar, React Server Components allow for more efficient rendering by offloading certain tasks to the server, while React Suspense offers a way to handle asynchronous operations more smoothly in your React applications. Intrigued? Let's dive deeper into these concepts

## React Server Components

The essence of React lies in its ability to provide composability and reusable custom building blocks. These components can be used across different parts of an application with minimal need for reconfiguration.

Consider a scenario where you have a `Post` component. This component takes `postId` as a prop and fetches and displays post content from an API. Traditionally, this process might look something like this:

```typescript
const Page = () => {
  return (
    <div>
      <Post postId={"post-1"} />
      <Post postId={"post-2"} />
      <Post postId={"post-3"} />
    </div>
  );
};

const Post = ({ postId }) => {
    useEffect(() => {
         // data fetching from an API using fetch or axios   
     }, [])
  return <>
  // displaying result
  </>;
};
```

Before Server Components, data fetching was often client-side, leading to visible loading states (like spinners) and reliance on the client's network speed and we used to get data using `fetch` like below.

```typescript
const Post = ({ postId }) => {
    const [loading, setLoading] = useState(false)
    const [data, setData] = useState()

    useEffect(() => {
    setLoading(true)
    fetch(`/api/post/${postId}`)
        .then(res => {
            setData(res.data)
        })
    setLoading(false)
    }, [])
  return <>{loading ? <Spinner /> : <div>{data.post}</div>}</>;
};
```

Using this way of data fetching, when we load our application on the client side, it will be rendered quickly. But once the page is rendered we will see `Spinner` when the data is being fetched on the client side. The data fetching on the client side may take longer than that depending on the network speed as well.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zsxcjga9dhj99zcimq0n.png)

Now, let's say we want to move data fetching to the server side instead of the client side to avoid having Spinner and dependency on the client's network to fetch the data. In that case, we'll use `getServerSideProps` and it will look like the below.

```typescript
export const getServerSideProps = async ({ params }) => {
  const { data } = await sql`SELECT * FROM POSTS WHERE id=${params.id}`;

  return {
    props: {
      data: data.post,
    },
  };
};

const Page = ({ data }) => {
  return (
    <div>
      <Post postData={data} />
    </div>
  );
};

const Post = ({ postData }) => {
  return <div>{postData}</div>;
};
```

Using this approach, as data is being fetched on the server inside `getServerSideProps`, when we load the application in the browser, we will see the browser loading state longer than the previous one. The reason is that it is fetching all the data on the server and due to that, we will not see the Spinner on the client side as well after the page has been rendered.

This approach is good and efficient in many ways but it makes our `Post` component dependent on the `getServerSideProps`. So, if we want to move our `Post` component somewhere else or maybe just reuse it elsewhere in a relatively lower child component then it will be dependent on the parent component to fetch and pass data to our `Post` component. Thus, it loses its composability which was the main feature of React in the first place. You will get more clarity by looking at the diagram below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqpr2ash0em3x4v7vqn4.png)

As shown in the above diagram, our `Post` component is not completely composable. Whenever we're trying to reuse it, it is expecting data from the parent component and thus it's dependent on it.

So, how do we make our component composable without being dependent on the server-side API and render it on the server side? Here is where React Server Components come into play. So, let's consider the above code block and make it a server component and also make sure that it is composable.

```typescript
const ServerCompPost = async ({ postId }) => {
  const { data } = await sql`SELECT * FROM POSTS WHERE id=${postId}`;
  return <div>{data.post}</div>;
};
```

`ServerCompPost` is a server-side component, self-sufficient in fetching its data. By default, every component in Nextjs is a server component, thus we don't have to do anything additional here to make it a server component. You can refer to their docs [here](https://nextjs.org/docs/app/building-your-application/rendering/server-components). Thus, we don't need to copy-paste `getServerSideProps` at all the places we will be using this component. It is totally composable and self-dependent for its data and doesn't require data to be passed as a prop from the parent component

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19r7tcvrq64ye77neyzq.png)

## React Suspense

Now, let's explore how React Suspense can further enhance user experience. Imagine a page rendering numerous `<Post />` components and a `<SimilarPosts />` component. `<SimilarPosts />` is another server component and is fully composable as shown below.

```jsx
const Page = () => {
  return (
    <div>
      <Post postId={"post-1"} />
      <Post postId={"post-2"} />
      <Post postId={"post-3"} />
      <SimilarPosts />
    </div>
  );
};
```

Thus, all this data fetching for all the `<Post />` components and `<SimilarPosts />` components are happening on the server side. Due to that, when we load our page in the browser it's taking a longer time as it is fetching data for all the components. Now, the user is gonna see **Similar Posts** only when he is going to scroll to the end after all the `<Post />`. Thus, he may or may not see Similar Posts. So, we can optimize that by server rendering that component only when the user scrolls to the bottom and that component is in the viewport.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubdogmo1hi1c6ix9c33s.png)

We can achieve that using `Suspense`. We need to wrap our component with the `Suspense` wrapper and show `<Spinner />` as a fallback. Below is the updated code.

```typescript
const Page = () => {
  return (
    <div>
      <Post postId={"post-1"} />
      <Post postId={"post-2"} />
      <Post postId={"post-3"} />
      <Suspense fallback={<Spinner />}>
        <SimilarPosts />
      </Suspense>
    </div>
  );
};
```

With Suspense, we defer rendering SimilarPosts until it's actually in view. This not only improves initial load times but also optimizes resources.

## Wrapping Up and Looking Ahead

In summary, React Server Components and React Suspense offer powerful ways to optimize both the performance and the user experience of web applications. By leveraging these features, developers can build more efficient, responsive, and user-friendly applications.

As we continue to see advancements in these technologies, it's exciting to imagine how they will shape the future of web development. Have you experimented with these features in your projects? Share your experiences and insights in the comments below!