Stuck Trying To Hydrate SSR Components In Raw HTML From Umbraco

- 1 answer

I have an Umbraco CMS loosely working with I've got a root <App> component which takes an array prop of (Umbraco Node ID and Path) objects, used to generated a <Route> for each page in the site using a <Content> comp. <App> is rendered in an UmbracoViewPage and renders fine in a client browser - with @Html.ReactInitJavaScript(), it gets hydrated as expected.

The <Content> component loads up the page HTML using dangerouslySetInnerHTML. I've got a few custom Umbraco grid editors which, instead of generating conventional markup in the ASP .NET view, instead call @Html.React() to render a custom React component which might take a prop or two.

On initial page load, the grid HTML is generated server-side and these nested components work fine in the browser, because is passing ReactDOM.hydrate() calls for each grid component (triggered by @Html.ReactInitJavaScript()).

BUT as soon as there is a frontend route change, the <App> mounts the relevant <Content> comp based on the path and this calls an Umbraco SurfaceController with the Umbraco Node ID. The page grid HTML for that node is returned and loaded as before using dangerouslySetInnerHTML.

The problem is I now have raw HTML which includes multiple React components which need hydrating. Because it's loading in the browser, there is no longer a @Html.ReactInitJavaScript() call hydrating all the components individually.

How can I achieve this?



Note: Answering my own question in case it helps anyone who is playing with Umbraco and React and is aiming for a tighter integration.

TL;DR - Include a list of component names, React IDs and props in my content API responses, allowing my frontend code to handle it much the same as ReactJS.NET's @Html.ReactInitJavaScript helper does (I think).

My solution was to modify my SurfaceController to include a list all of the React components rendered in the markup. So it does much the same as ReactJS.NET's @Html.ReactInitJavaScript helper but in my SurfaceController API method.

I extended ReactJS.NET's @Html.React helper so that details about the component called are also added to a custom helper service I named ReactComponentHydrator (gist, per-request singleton)*. For each component, I store the component name (e.g. Component.ExampleComponent), the React ID included in the HTML markup (e.g. react_d1gC4eKqaUatZWfAdtjkTA) and the props sent with it.

From that, in the frontend code I am to create and hydrate each component once the HTML has been loaded via dangerouslySetInnerHTML.

*ReactJS.NET does seem to keep track of each component called but this is internal and not exposed through any API I could find.