How to Setup React Native Web in a Remix project
Published: 2022-02-07 19:18:58

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](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`.

```bash
$ 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.

```bash
$ 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:

```tsx
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. 

```tsx
...

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. 

```css
html, body {
  height: 100%;
}

body {
  display: flex;
}
```

_Take a look at the [Remix Docs about Stylesheets](https://remix.run/docs/en/v1/guides/styling#regular-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:

```tsx
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:

```tsx
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:

```tsx
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](https://twitter.com/HorusGoul)