Created
September 12, 2023 15:07
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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