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"; import { hydrate } from "react-dom"; import { AppRegistry } from "react-native"; import { ReactNativeStylesContext } from "./rn-styles"; const App = () => <RemixBrowser />; AppRegistry.registerComponent("App", () => App); // @ts-ignore const { getStyleElement } = AppRegistry.getApplication("App"); hydrate( <ReactNativeStylesContext.Provider value={getStyleElement()}> <App /> </ReactNativeStylesContext.Provider>, document );
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"; import type { EntryContext } from "remix"; 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
Top comments (1)
how about use preferred .web.ts as react-native multi platform naming style?
like this
MyComponent.android.ts
MyComponent.ios.ts
MyComponent.web.ts
will prefer to use MyComponent.web.ts
it is very useful in platform specific conditions.