DEV Community

Horus Lugo
Horus Lugo

Posted on • Updated on • Originally published at horus.dev

How to Setup React Native Web in a Remix project

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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" />;
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

We also need to add a global stylesheet to Remix that contains the following.

html, body {
  height: 100%;
}

body {
  display: flex;
}
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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,
  });
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
blackbing profile image
Bingo Yang

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.