How to Setup React Native Web in a Remix project
Easy way to use React Native components in your Remix projects and reuse code#react#remix#react-nativeFebruary 7 · 3 min read
I assume you're here because you want to know how to set up React Native Web in your Remix project. Well, you're lucky, I had to do this a few days ago, and I haven't run into trouble with it yet, so here's a tutorial about it:
Let's get started!
The result of this tutorial is also available as a GitHub repository that you can just clone to get started with your project: https://github.com/HorusGoul/remix-react-native-web-starter
1. Installing the react-native-web package
The first thing you have to do is install the react-native-web
package. However, since we can't customize our build process because Remix doesn't allow it yet, we'll need to use a package manager that will enable you to install a package with an alias. In this case, I decided to use pnpm
.
$ pnpm add react-native@npm:react-native-web
Then, the types if you're using TypeScript. Note that not all types will be correct for a React Native Web project, but that's out of the scope of this tutorial.
$ pnpm add --save-dev @types/react-native
2. React Native Web Styles
React Native Web has its own way of handling styles, and for SSR and hydration, they give you a stylesheet element that you have to place in the <head>
of your page.
We'll pass that stylesheet element to our Root using the Context API. Let's do that by creating a rn-styles.tsx
file inside the app
folder. Here's the content for that file:
import { createContext, useContext } from "react";
export const ReactNativeStylesContext =
createContext<React.ReactElement<unknown> | null>(null);
export function useReactNativeStyles() {
return useContext(ReactNativeStylesContext) ?? ReplaceWithStylesSSRTag;
}
export const ReplaceWithStylesSSRTag = <meta name="REPLACE_WITH_STYLES" />;
Now, let's move to the app/root.tsx
file. We'll now use the useReactNativeStyles()
hook and put the stylesElement
inside the <head>
. Also, we'll wrap the <Outlet />
component with a View with a few properties to match the React Native Web behavior.
...
import { useReactNativeStyles } from "./rn-styles";
import { View, StyleSheet } from "react-native";
...
export default function App() {
const stylesElement = useReactNativeStyles();
return (
<html lang="en">
<head>
...
{stylesElement}
</head>
<body>
...
<View pointerEvents="box-none" style={styles.appContainer}>
<Outlet />
</View>
...
</body>
</html>
);
}
const styles = StyleSheet.create({
appContainer: {
flex: 1,
},
});
We also need to add a global stylesheet to Remix that contains the following.
html, body {
height: 100%;
}
body {
display: flex;
}
Take a look at the Remix Docs about Stylesheets if you don't know how to add CSS in a Remix project.
3. SSR and Hydration
Assuming you haven't modified the app/entry.client.tsx
file, just replace its contents with the following:
import { RemixBrowser } from "@remix-run/react"";
import { hydrateRoot } from "react-dom/client";
import { AppRegistry } from "react-native";
import { ReactNativeStylesContext } from "./rn-styles";
const App = () => <RemixBrowser />;
AppRegistry.registerComponent("App", () => App);
// @ts-ignore
const { getStyleElement } = AppRegistry.getApplication("App");
hydrateRoot(
document,
<ReactNativeStylesContext.Provider value={getStyleElement()}>
<App />
</ReactNativeStylesContext.Provider>
);
What's going on with that code? We're using React Native Web AppRegistry
to get the initial styles for the app and avoid hydration from failing.
Now, let's move to the app/entry.server.tsx
file, where we'll do a similar thing. Take a look at the code:
import { renderToString, renderToStaticMarkup } from "react-dom/server";
import { RemixServer } from "@remix-run/react"";
import type { EntryContext } from "@remix-run/node";
import { AppRegistry } from "react-native";
import { ReplaceWithStylesSSRTag } from "./rn-styles";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const App = () => <RemixServer context={remixContext} url={request.url} />;
AppRegistry.registerComponent("App", () => App);
let markup = renderToString(<App />);
// @ts-ignore
const { getStyleElement } = AppRegistry.getApplication("App", {});
const stylesMarkup = renderToStaticMarkup(getStyleElement());
markup = markup.replace(
renderToStaticMarkup(ReplaceWithStylesSSRTag),
stylesMarkup
);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
This time, we're not passing the app styles using the ReactNativeStylesContext, but injecting them inside the markup by replacing a custom meta tag.
4. Using React Native Web components
One last thing you could do if you're doing this in a new project is to go ahead and replace your app/routes/index.tsx
with the following:
import { Text, View } from "react-native";
export default function Index() {
return (
<View>
<Text>Hello, world!</Text>
</View>
);
}
Then do pnpm dev
and open your project in the browser to see your first Remix route rendered with React Native Web components!
I hope you liked this article! Don't forget to follow me on Twitter if you want to know when I publish something new about web development: @HorusGoul