Skip to content

Instantly share code, notes, and snippets.

@thomaswelton
Created September 12, 2023 15:07
Show Gist options
  • Save thomaswelton/e135d2fcf8ee9cce031b3c82d8daa5d9 to your computer and use it in GitHub Desktop.
Save thomaswelton/e135d2fcf8ee9cce031b3c82d8daa5d9 to your computer and use it in GitHub Desktop.
RouterContext component. Takes the StaticHandlerContext from React Router and adds support for handling promises which come from deferred data loaders.
// @flow
/* eslint-disable react/no-danger */
import React, { Suspense, useMemo } from 'react';
import { Await, useAsyncError, useAsyncValue } from 'react-router-dom';
import type { StaticHandlerContext } from '@remix-run/router/router';
type KeyProps = {
itemKey: string,
itemSubkey: string
};
type PromiseProps = {
itemKey: string,
itemSubkey: string,
promise: Promise<any>
};
type RouterProps = {
routerContext: StaticHandlerContext
};
const getPromises = (routerContext) => {
const loaderDataKeys = Object.keys(routerContext.loaderData);
const deferPromises = [];
loaderDataKeys.forEach((key) => {
const itemData = routerContext.loaderData[key];
if (itemData instanceof Object) {
const keys = Object.keys(itemData);
keys.forEach((subkey) => {
const item = itemData[subkey];
const isPromise =
item instanceof Object &&
'then' in item &&
typeof item.then === 'function';
if (isPromise) {
deferPromises.push({
key,
subkey,
promise: item
});
}
});
}
});
return deferPromises;
};
const RouterPromiseResolve = ({ itemKey, itemSubkey }: KeyProps) => {
const data = useAsyncValue();
return (
<script
dangerouslySetInnerHTML={{
__html: `window.__staticRouterHydrationDataPromises['${itemKey}']['${itemSubkey}'].resolve(${JSON.stringify(
data
)})`
}}
/>
);
};
const RouterPromiseError = ({ itemKey, itemSubkey }: KeyProps) => {
const data = useAsyncError();
return (
<script
dangerouslySetInnerHTML={{
__html: `window.__staticRouterHydrationDataPromises['${itemKey}']['${itemSubkey}'].reject(${JSON.stringify(
data
)})`
}}
/>
);
};
const RouterPromise = ({ itemKey, itemSubkey, promise }: PromiseProps) => (
<Suspense
fallback={
<script
dangerouslySetInnerHTML={{
__html: `
window.__staticRouterHydrationDataPromises = window.__staticRouterHydrationDataPromises || {};
window.__staticRouterHydrationData.loaderData['${itemKey}']['${itemSubkey}'] = new Promise(
(resolve, reject) => {
window.__staticRouterHydrationDataPromises['${itemKey}'] = window.__staticRouterHydrationDataPromises['${itemKey}'] || {};
window.__staticRouterHydrationDataPromises['${itemKey}']['${itemSubkey}'] = {
'resolve': resolve,
'reject': reject
};
}
);
`
}}
/>
}
>
<Await
resolve={promise}
errorElement={
<RouterPromiseError itemKey={itemKey} itemSubkey={itemSubkey} />
}
>
<RouterPromiseResolve itemKey={itemKey} itemSubkey={itemSubkey} />
</Await>
</Suspense>
);
const RouterContext = ({ routerContext }: RouterProps) => {
const { context, promises } = useMemo(() => {
const { loaderData, actionData, errors } = routerContext;
return {
context: {
loaderData,
actionData,
errors
},
promises: getPromises(routerContext)
};
}, [routerContext]);
return (
<>
<script
dangerouslySetInnerHTML={{
__html: `window.__staticRouterHydrationData=${JSON.stringify(
context
)}`
}}
/>
{promises.map(({ key, subkey, promise }) => (
<RouterPromise
key={`${key}-${subkey}`}
itemKey={key}
itemSubkey={subkey}
promise={promise}
/>
))}
</>
);
};
export default RouterContext;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment