harvest time

React Server Components in React Native

What are React Server Components?

React Server Components, or RSC for short, is a new feature introduced by the ReactJS team in version 19.

RSC definition according to their doc is:

Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server.

So, what are the benefits of using RSC?

  1. Data fetching with async components and React Suspense.
  2. Working with secrets and server-side APIs.
  3. Server-side rendering (SSR) for SEO and performance.
  4. Build-time rendering to remove unused JS code.

Basically, RSC helps with bundling size and reduces client-side waterfall when requesting data from the API.

Implement RSC in React Native

Without further ado, let's implement RSC in React Native.

We will create a simple application to render post data from jsonplaceholder database. We will also need to render the comments of each post, displaying the total number of comments.

Pre-requisites:

  • Expo based React Native
    • Shameless plug: If you forget how to create a lot of small projects, try installing my global package `npm -g i @rustyrush/thecreate`
    • Install default Expo based RN: `thecreate --fw=reactnative`
    • You can remove the default template code with `bun run reset-project`
  • Add this in `app.json`
{
"experiments": {
"reactServerFunctions": true
},
"web": {
"output": "server"
}
}

Now this will give you a clean sheet of React Native project. Let's dive in!

Starting with `app/index.js`, we are going to use React Suspense together with RSC. Suspense is smart enough to understand when the data is available.

export default function HomeScreen() {
return (
<SafeAreaView>
<React.Suspense fallback={<ActivityIndicator />}>
{renderPosts()}
</React.Suspense>
</SafeAreaView>
)
}

Next, let's define the `renderPosts` function. This function is a server function, so we need to add `server-only` directive on top of the file.

Note: I can't use 'use server' directive here, Metro complains that it was wrong!
'server-only'
import { FlatList } from 'react-native'
import Item from '@/components/item'
import { PostType } from '@/types/api'
async function fetchPosts() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
const data: PostType[] = await res.json()
return data
}
async function renderPosts() {
const posts = await fetchPosts()
return (
<>
<FlatList
data={posts}
renderItem={({ item }) => {
return <Item item={item} />
}}
keyExtractor={(item) => item.id.toString()}
initialNumToRender={data.length}
/>
</>
)
}
export default renderPosts

Notice that our renderPosts is an async function and it returns JSX! So with server function, you can do this, and this code will run on the server and stream the JSX to the client, which is quite a neat approach.

This way, you don't need to worry about fetching data on the client with useState and useEffect!

I leave creating `Item.tsx` component to you! It is basically just a client component that renders the data.

Serial / Parallel Fetching

With RSC, you can then do other fetching necessary on this component. You can do serial fetching, parallel fetching, anything you like. This won't create any client-side waterfall where client needs to fetch posts data and then it will trigger another fetch to fetch the comments.

Let's see how it works.

import { PostType, PostWithComment } from '@/types/api'
// async function fetchPosts()...
async function fetchComments(posts: PostType[]) {
const commentPromise = posts.map((post) =>
fetch(
`https://jsonplaceholder.typicode.com/posts/${post.id}/comments`
).then((res) => res.json())
)
const commentRes = await Promise.all(commentPromise)
return commentRes
}
async function renderPosts() {
const posts = await fetchPosts()
const commentRes = await fetchComments(posts)
const data: PostWithComment[] = posts.map((p, i) => ({
...p,
comments: commentRes[i],
}))
// ...rest of code
// just change
// data={data}
}

This is pretty simple and powerful; this way, users have a better experience with a shorter loading time.

Conclusion

RSC is still new and experimental. If you are creating a web application, you can use a simple app with ParcelJS or NextJS.

In my opinion, this pattern takes a lot of new approaches from the perspective of your mental model. It is a very different approach, and it has its own rules along with React rules, such as you can't render RSC inside a client component.

RSC won't solve any problem if your app is small and mostly static, but RSC opens many opportunities when your application gets bigger and every little component needs to talk to the API.

At last, I encourage you to try it and understand the benefits of RSC and how it works!

Happy coding!

Other resources:

Expo Documentation

Go back