Skip to content

Instantly share code, notes, and snippets.

@jarkkosyrjala
Created October 15, 2020 20:00
Show Gist options
  • Save jarkkosyrjala/cac1b827a52d210dac983745c4f21861 to your computer and use it in GitHub Desktop.
Save jarkkosyrjala/cac1b827a52d210dac983745c4f21861 to your computer and use it in GitHub Desktop.
Render static content as HTML and get the content from server side rendered HTML back as props before hydration
import * as React from 'react'
import DynamicAppWithStaticSections, {
DynamicAppWithStaticSectionsProps,
DynamicAppWithStaticSectionsHydrationProps,
} from './DynamicAppWithStaticSections'
import { hydrate } from 'react-dom'
interface AppWindow extends Window {
__APP__: DynamicAppWithStaticSectionsHydrationProps
}
declare const window: AppWindow
const partiallyStaticComponentProps: DynamicAppWithStaticSectionsProps = {
// Only part of the props are passed to JSON in DOM
...window.__APP__,
// Get the rendered html and pass it back as props
staticSections: Array.from(
document.querySelectorAll('.static-sections > section > div'),
(div) => div.innerHTML,
),
}
hydrate(
<DynamicAppWithStaticSections {...partiallyStaticComponentProps} />,
document.getElementById('app'),
)
import * as React from 'react'
import { renderToStaticMarkup } from 'react-dom/server'
import { useState } from 'react'
export interface DynamicAppWithStaticSectionsProps {
dynamicText: string
staticSections: string[]
}
export type DynamicAppWithStaticSectionsHydrationProps = Omit<
DynamicAppWithStaticSectionsProps,
'staticSections'
>
interface SectionWithStaticContentProps {
html: string
}
const SectionWithStaticContent: React.FC<SectionWithStaticContentProps> = ({ html }) => {
const [show, setShow] = useState<boolean>(true)
return (
<section>
<button onClick={() => setShow(!show)}>Toggle</button>
<div
style={{ display: show ? 'block' : 'none' }}
dangerouslySetInnerHTML={{ __html: html }}
/>
}
</section>
)
}
const DynamicAppWithStaticSections: React.FC<DynamicAppWithStaticSectionsProps> = ({
dynamicText,
staticSections,
}) => {
return (
<>
<div>{renderToStaticMarkup(<>{dynamicText}</>)}</div>
<div className="static-sections">
{staticSections.map((html) => (
<SectionWithStaticContent html={html} />
))}
</div>
</>
)
}
export default DynamicAppWithStaticSections
import * as React from 'react'
import { DynamicAppWithStaticSectionsProps } from './DynamicAppWithStaticSections'
export interface HtmlPageProps {
partiallyStaticComponent: DynamicAppWithStaticSectionsProps
}
/**
* Html template that prints the hydrations props as JSON
*/
const Html: React.FC<HtmlPageProps> = ({ partiallyStaticComponent, children }) => {
let { staticSections: _omit, ...hydrationProps } = partiallyStaticComponent
return (
<html>
<body>
<h1>Example</h1>
<div id="app">{children}</div>
<script
dangerouslySetInnerHTML={{
__html: `window.__APP__ = ${JSON.stringify(hydrationProps)}`,
}}
/>
</body>
</html>
)
}
export default Html
import { NextFunction, Request, Response } from 'express-serve-static-core'
import * as React from 'react'
import DynamicAppWithStaticSections, {
DynamicAppWithStaticSectionsProps,
} from './DynamicAppWithStaticSections'
import { renderToStaticMarkup, renderToString } from 'react-dom/server'
import Html from './Html'
const getPartiallyStaticComponentProps = async (): Promise<DynamicAppWithStaticSectionsProps> => {
// Fetch from server etc...
return Promise.resolve({
dynamicText: 'foo',
staticSections: ['Hello', 'World'],
})
}
const exampleExpressRouteHandler = async (
_req: Request,
res: Response,
_next: NextFunction,
): Promise<any> => {
// get the props from somewhere
const partiallyStaticComponentProps = await getPartiallyStaticComponentProps()
return res.send(
renderToString(
<Html partiallyStaticComponent={partiallyStaticComponentProps}>
<DynamicAppWithStaticSections
{...{
...partiallyStaticComponentProps,
staticSections: partiallyStaticComponentProps.staticSections.map((content) =>
renderToStaticMarkup(<div className="fancy-but-static-sub-component">{content}</div>),
),
}}
/>
</Html>,
),
)
}
@jarkkosyrjala
Copy link
Author

Of course when rendering purely static content then the component does not need to be hydrated at all. However, when ever there's anything interactive then hydration is needed and for hydration to work without re-render, the props need to match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment