remix-utils

A set of utility functions and types to use with Remix.run

README

Remix Utils


This package contains simple utility functions to use with Remix.run.

Installation


  1. ```bash
  2. npm install remix-utils
  3. ```

API Reference


promiseHash


The promiseHash function is not directly related to Remix but it's a useful function when working with loaders and actions.

This function is an object version of Promise.all which lets you pass an object with promises and get an object with the same keys with the resolved values.

  1. ```ts
  2. export async function loader({ request }: LoaderArgs) {
  3.   return json(
  4.     await promiseHash({
  5.       user: getUser(request),
  6.       posts: getPosts(request),
  7.     })
  8.   );
  9. }
  10. ```

You can use nested promiseHash to get a nested object with resolved values.

  1. ```ts
  2. export async function loader({ request }: LoaderArgs) {
  3.   return json(
  4.     await promiseHash({
  5.       user: getUser(request),
  6.       posts: promiseHash({
  7.         list: getPosts(request),
  8.         comments: promiseHash({
  9.           list: getComments(request),
  10.           likes: getLikes(request),
  11.         }),
  12.       }),
  13.     })
  14.   );
  15. }
  16. ```

timeout


The timeout function lets you attach a timeout to any promise, if the promise doesn't resolve or reject before the timeout, it will reject with a TimeoutError.

  1. ```ts
  2. try {
  3.   let result = await timeout(fetch("https://example.com"), { ms: 100 });
  4. } catch (error) {
  5.   if (error instanceof TimeoutError) {
  6.     // Handle timeout
  7.   }
  8. }
  9. ```

Here the fetch needs to happen in less than 100ms, otherwise it will throw a TimeoutError.

If the promise is cancellable with an AbortSignal you can pass the AbortController to the timeout function.

  1. ```ts
  2. try {
  3.   let controller = new AbortController();
  4.   let result = await timeout(
  5.     fetch("https://example.com", { signal: controller.signal }),
  6.     { ms: 100, controller }
  7.   );
  8. } catch (error) {
  9.   if (error instanceof TimeoutError) {
  10.     // Handle timeout
  11.   }
  12. }
  13. ```

Here after 100ms, timeout will call controller.abort() which will mark the controller.signal as aborted.

cacheAssets


Note

This can only be run inside entry.client.


This function lets you easily cache inside the browser's Cache Storage every JS file built by Remix.

To use it, open your entry.client file and add this:

  1. ```ts
  2. import { cacheAssets } from "remix-utils";

  3. cacheAssets().catch((error) => {
  4.   // do something with the error, or not
  5. });
  6. ```

The function receives an optional options object with two options:

- cacheName is the name of the Cache object to use, the default value isassets.
- buildPath is the pathname prefix for all Remix built assets, the default value is /build/ which is the default build path of Remix itself.

It's important that if you changed your build path in remix.config.js you pass the same value to cacheAssets or it will not find your JS files.

The cacheName can be left as is unless you're adding a Service Worker to your app and want to share the cache.

  1. ```ts
  2. cacheAssests({ cacheName: "assets", buildPath: "/build/" }).catch((error) => {
  3.   // do something with the error, or not
  4. });
  5. ```

ClientOnly


The ClientOnly component lets you render the children element only on the client-side, avoiding rendering it the server-side.

Note

If you're using React 18 and a streaming server rendering API (eg. [renderToPipeableStream](https://beta.reactjs.org/reference/react-dom/server/renderToPipeableStream)) you probably want to use a <Suspense> boundary instead.

>

tsx

export default function Component() {

return (

<Suspense fallback={<SimplerStaticVersion />}>

<ComplexComponentNeedingBrowserEnvironment />

</Suspense>

);

}

>

You can provide a fallback component to be used on SSR, and while optional, it's highly recommended to provide one to avoid content layout shift issues.

  1. ```tsx
  2. import { ClientOnly } from "remix-utils";

  3. export default function Component() {
  4.   return (
  5.     <ClientOnly fallback={<SimplerStaticVersion />}>
  6.       {() => <ComplexComponentNeedingBrowserEnvironment />}
  7.     </ClientOnly>
  8.   );
  9. }
  10. ```

This component is handy when you have some complex component that needs a browser environment to work, like a chart or a map. This way, you can avoid rendering it server-side and instead use a simpler static version like an SVG or even a loading UI.

The rendering flow will be:

- SSR: Always render the fallback.
- CSR First Render: Always render the fallback.
- CSR Update: Update to render the actual component.
- CSR Future Renders: Always render the actual component, don't bother to render the fallback.

This component uses the useHydrated hook internally.

ServerOnly


The ServerOnly component is the opposite of the ClientOnly component, it lets you render the children element only on the server-side, avoiding rendering it the client-side.

You can provide a fallback component to be used on CSR, and while optional, it's highly recommended to provide one to avoid content layout shift issues, unless you only render visually hidden elements.

  1. ```tsx
  2. import { ServerOnly } from "remix-utils";

  3. export default function Component() {
  4.   return (
  5.     <ServerOnly fallback={<ComplexComponentNeedingBrowserEnvironment />}>
  6.       {() => <SimplerStaticVersion />}
  7.     </ServerOnly>
  8.   );
  9. }
  10. ```

This component is handy to render some content only on the server-side, like a hidden input you can later use to know if JS has loaded.

Consider it like the `