React Cosmos with Remix

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:

export default function Cosmos() {
    return null;
}

Add cosmos.config.json in your project root directory:

{
    "staticPath": "public",
    "watchDirs": ["app"],
    "userDepsFilePath": "app/cosmos.userdeps.js",
    "experimentalRendererUrl": "http://localhost:3000/cosmos"
}

Change your entry.client.tsx accordingly:

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:

$ npm i -D react-cosmos

Add the following in your package.json scripts section:

"cosmos": "cosmos",
    "cosmos:export": "cosmos-export"

Start the remix dev server:

$ npm run dev

Start cosmos server in another terminal window:

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

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:

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.

15