React Cosmos with Remix
Tags: react, typescript, remix
Published: 2021-12-09 09:32:07

I recently discovered https://remix.run. Whoa, haven't been this excited over a framework for a long time. Naturally I already switched some pet projects over and the development flow has been very straightforward.

One topic of interest of mine is how to speed up and isolate developing of components that I use in my apps.

Enter https://reactcosmos.org. It's an alternative to Storybook and to me looks a bit cleaner with smaller amount of boilerplate needed to get running out of the box. It runs a separate dev server with a clean UI displaying all of your component fixtures.

So I tried to pair my Remix app with React Cosmos so I would not have to run a separate Webpack bundler in order to get the fixtures update as I work on the components. I got it working by following the Snowpack example from React-Cosmos Github space.

The first draft of this tutorial I posted also under an open issue about supporting StoryBook in Remix Github issues page: https://github.com/remix-run/remix/issues/214

Create `cosmos.tsx` under `app/routes`:
```tsx
export default function Cosmos() {
    return null;
}
```

Add `cosmos.config.json` in your project root directory:
```json
{
    "staticPath": "public",
    "watchDirs": ["app"],
    "userDepsFilePath": "app/cosmos.userdeps.js",
    "experimentalRendererUrl": "http://localhost:3000/cosmos"
}
```

Change your `entry.client.tsx` accordingly:
```tsx
import { mountDomRenderer } from "react-cosmos/dom";
import { hydrate } from "react-dom";
import { RemixBrowser } from "remix";

import { decorators, fixtures, rendererConfig } from "./cosmos.userdeps.js";

if (
    process.env.NODE_ENV === "development" &&
    window.location.pathname.includes("cosmos")
) {
    mountDomRenderer({ rendererConfig, decorators, fixtures });
} else {
    hydrate(<RemixBrowser />, document);
}
```
You might need to add `// @ts-nocheck` in the beginning of this file if using Typescript (you should), because TS will likely complain about not finding `./cosmos.userdeps.js` which will be generated automatically by React Cosmos on each run. Oh and you should add that file to your `.gitignore` file as well!


Of course, add react-cosmos as a dev dependency:
```bash
$ npm i -D react-cosmos
```

Add the following in your `package.json` scripts section:
```json
    "cosmos": "cosmos",
    "cosmos:export": "cosmos-export"
```

Start the remix dev server:
```zsh
$ npm run dev
```

Start cosmos server in another terminal window:
```zsh
$ npm run cosmos
```

Now, although this works, I noticed in the developer console, that my remix app started polling itself and getting 404 periodically because of a socket.io route was not configured.

This started to bother me so I investigated further and found a cleaner solution:

In `app/routes/cosmos.tsx` make the following changes:
```tsx
import { useCallback, useState } from "react";
import { useEffect } from "react";
import { HeadersFunction } from "remix";
import { decorators, fixtures, rendererConfig } from "~/cosmos.userdeps.js";

const shouldLoadCosmos =
    typeof window !== "undefined" && process.env.NODE_ENV === "development";

export const headers: HeadersFunction = () => {
    return { "Access-Control-Allow-Origin": "*" };
};

export default function Cosmos() {
    const [cosmosLoaded, setCosmosLoaded] = useState(false);
    const loadRenderer = useCallback(async () => {
        const { mountDomRenderer } = (await import("react-cosmos/dom")).default;
        mountDomRenderer({ decorators, fixtures, rendererConfig });
    }, []);

    useEffect(() => {
        if (shouldLoadCosmos && !cosmosLoaded) {
            loadRenderer();
            setCosmosLoaded(true);
        }
    }, []);
    return null;
}
```

And restore your `entry.client.ts` file to it's original state:
```tsx
import { hydrate } from "react-dom";
import { RemixBrowser } from "remix";

hydrate(<RemixBrowser />, document);
```

So there you have it - Remix dev server running on localhost:3000 and React Cosmos server running on localhost:5000.

Notice the headers function export in `app/routes/cosmos.tsx` - I added that so there would be no annoying cors errors in your dev console, although it seemed to work perfectly fine without it as well.