Skip to content

Commit

Permalink
feat: add with-rsc demo (#539)
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanBacon authored Jan 15, 2025
1 parent 630e755 commit 799290c
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 0 deletions.
1 change: 1 addition & 0 deletions with-rsc/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MY_SECRET=123
14 changes: 14 additions & 0 deletions with-rsc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Expo Router React Server Components Example

Use [`expo-router`](https://docs.expo.dev/router/introduction/) with experimental React Server Components to create a simple app.

## 🚀 How to use

```sh
npx create-expo-app -e with-rsc
```

## 📝 Notes

- [Expo Router: Docs](https://docs.expo.dev/router/introduction/)
- [Expo Router: React Server Components](https://docs.expo.dev/guides/server-components/)
9 changes: 9 additions & 0 deletions with-rsc/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"expo": {
"scheme": "acme",
"plugins": ["expo-router"],
"experiments": {
"reactServerFunctions": true
}
}
}
5 changes: 5 additions & 0 deletions with-rsc/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Stack } from "expo-router";

export default function Layout() {
return <Stack />;
}
41 changes: 41 additions & 0 deletions with-rsc/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// <reference types="react/canary" />

// This route is a client component.

import { StyleSheet, Text, View } from "react-native";
import { renderHomeAsync } from "../functions/render-home";
import React from "react";

export default function Page() {
return (
<View style={styles.container}>
<View style={styles.main}>
<React.Suspense fallback={<Text>Loading...</Text>}>
{renderHomeAsync()}
</React.Suspense>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
padding: 24,
},
main: {
flex: 1,
justifyContent: "center",
maxWidth: 960,
marginHorizontal: "auto",
},
title: {
fontSize: 64,
fontWeight: "bold",
},
subtitle: {
fontSize: 36,
color: "#38434D",
},
});
35 changes: 35 additions & 0 deletions with-rsc/functions/render-home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use server";

// This file runs in the server and can return server components.

// Optionally: You can import "server-only" to ensure this file is ne
import "server-only";
import { Stack } from "expo-router";

import { Image, Text, View } from "react-native";

export async function renderHomeAsync() {
// Secrets can be used in the server.
console.log("Secret:", process.env.MY_SECRET);

const res = await fetch("https://pokeapi.co/api/v2/pokemon/2");

const json = await res.json();

return (
<>
<Stack.Screen options={{ title: json.name }} />
<View style={{ padding: 8, borderWidth: 1 }}>
<Text style={{ fontWeight: "bold", fontSize: 24 }}>{json.name}</Text>
<Image
source={{ uri: json.sprites.front_default }}
style={{ width: 100, height: 100 }}
/>

{json.abilities.map((ability) => (
<Text key={ability.ability.name}>- {ability.ability.name}</Text>
))}
</View>
</>
);
}
31 changes: 31 additions & 0 deletions with-rsc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "with-router",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start"
},
"dependencies": {

"@types/react": "~18.3.12",
"expo": "^52.0.16",
"expo-constants": "~17.0.2",
"expo-linking": "~7.0.2",
"expo-router": "~4.0.2",
"expo-splash-screen": "~0.29.7",
"expo-status-bar": "~2.0.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-native": "0.76.3",
"react-native-gesture-handler": "~2.20.2",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.1.0",
"react-native-web": "~0.19.6",
"typescript": "^5.3.3",
"react-server-dom-webpack": "19.0.0-rc-6230622a1a-20240610"
},
"devDependencies": {
"patch-package": "^8.0.0",
"@babel/core": "^7.20.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js
index c9bc9ea..cf4eebd 100644
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js
@@ -2113,7 +2113,7 @@ function createModelReject(chunk) {
function createServerReferenceProxy(response, metaData) {
var callServer = response._callServer;

- var proxy = function () {
+ var proxy = async function () {
// $FlowFixMe[method-unbinding]
var args = Array.prototype.slice.call(arguments);
var p = metaData.bound;
@@ -2128,10 +2128,10 @@ function createServerReferenceProxy(response, metaData) {
} // Since this is a fake Promise whose .then doesn't chain, we have to wrap it.
// TODO: Remove the wrapper once that's fixed.

-
- return Promise.resolve(p).then(function (bound) {
- return callServer(metaData.id, bound.concat(args));
- });
+ // HACK: This is required to make native server actions return a non-undefined value.
+ // Seems like a bug in the Hermes engine since the same babel transforms work in Chrome/web.
+ const _bound = await p;
+ return callServer(metaData.id, _bound.concat(args));
};

registerServerReference(proxy, metaData);
diff --git a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js
index 6a7df37..9a816c3 100644
--- a/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js
+++ b/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js
@@ -696,18 +696,30 @@ function createModelReject(chunk) {
};
}
function createServerReferenceProxy(response, metaData) {
- function proxy() {
+ var callServer = response._callServer;
+
+ var proxy = async function () {
+ // $FlowFixMe[method-unbinding]
var args = Array.prototype.slice.call(arguments),
p = metaData.bound;
- return p
- ? "fulfilled" === p.status
- ? callServer(metaData.id, p.value.concat(args))
- : Promise.resolve(p).then(function (bound) {
- return callServer(metaData.id, bound.concat(args));
- })
- : callServer(metaData.id, args);
- }
- var callServer = response._callServer;
+
+ if (!p) {
+ return callServer(metaData.id, args);
+ }
+
+ if (p.status === "fulfilled") {
+ var bound = p.value;
+ return callServer(metaData.id, bound.concat(args));
+ }
+ // Since this is a fake Promise whose .then doesn't chain, we have to wrap it.
+ // TODO: Remove the wrapper once that's fixed.
+
+ // HACK: This is required to make native server actions return a non-undefined value.
+ // Seems like a bug in the Hermes engine since the same babel transforms work in Chrome/web.
+ const _bound = await p;
+ return callServer(metaData.id, _bound.concat(args));
+ };
+
knownServerReferences.set(proxy, metaData);
return proxy;
}
4 changes: 4 additions & 0 deletions with-rsc/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"compilerOptions": {},
"extends": "expo/tsconfig.base"
}

0 comments on commit 799290c

Please sign in to comment.