feat(react-renderer): Nested layout support for react-renderer (#449)

* feat: nested layout support for react-renderer

* refactor

* feat: describe changeset
pull/457/head
Yuto Yoshino 2024-04-16 20:58:39 +09:00 committed by GitHub
parent ffd67e5412
commit 284904357f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 99 additions and 8 deletions

View File

@ -0,0 +1,5 @@
---
'@hono/react-renderer': minor
---
nested layout support for react-renderer

View File

@ -39,7 +39,7 @@
"@types/react": "^18",
"@types/react-dom": "^18.2.17",
"esbuild": "^0.20.2",
"hono": "^3.11.7",
"hono": "^4.2.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsup": "^8.0.1",

View File

@ -15,14 +15,21 @@ type BaseProps = {
children: React.ReactElement
}
type ComponentProps = Props &
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BaseProps & { Layout: React.FC<Record<string, any> & { children: React.ReactElement }> }
const RequestContext = React.createContext<Context | null>(null)
const createRenderer =
(c: Context, component?: React.FC<Props & BaseProps>, options?: RendererOptions) =>
(
c: Context,
Layout: React.FC<{ children: React.ReactElement }>,
component?: React.FC<ComponentProps>,
options?: RendererOptions
) =>
async (children: React.ReactElement, props?: Props) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const node = component ? component({ children, c, ...props }) : children
const node = component ? component({ children, Layout, c, ...props }) : children
if (options?.stream) {
const { renderToReadableStream } = await import('react-dom/server')
@ -53,11 +60,20 @@ const createRenderer =
}
export const reactRenderer = (
component?: React.FC<Props & BaseProps>,
component?: React.FC<ComponentProps>,
options?: RendererOptions
): MiddlewareHandler =>
function reactRenderer(c, next) {
c.setRenderer(createRenderer(c, component, options))
const Layout = (c.getLayout() ?? React.Fragment) as React.FC<{
children: React.ReactElement
}>
if (component) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
c.setLayout((props: any) => {
return component({ ...props, Layout, c }, c)
})
}
c.setRenderer(createRenderer(c, Layout, component, options))
return next()
}

View File

@ -57,6 +57,69 @@ describe('Basic', () => {
expect(await res.text()).toBe('<h1>http://localhost/</h1>')
})
it('nested layout with Layout', async () => {
const app = new Hono()
app.use(
'*',
// @ts-expect-error - `title` is not defined
reactRenderer(({ children, title, Layout }) => {
return (
<Layout>
<html>
<head>{title}</head>
<body>{children}</body>
</html>
</Layout>
)
})
)
const app2 = new Hono()
app2.use(
'*',
// @ts-expect-error - `title` is not defined
reactRenderer(({ children, Layout, title }) => {
return (
<Layout title={title}>
<div className='nested'>{children}</div>
</Layout>
)
})
)
app2.get('/', (c) => c.render(<h1>http://localhost/nested</h1>, { title: 'Nested' }))
const app3 = new Hono()
app3.use(
'*',
// @ts-expect-error - `title` is not defined
reactRenderer(({ children, Layout, title }) => {
return (
<Layout title={title}>
<div className='nested2'>{children}</div>
</Layout>
)
})
)
app3.get('/', (c) => c.render(<h1>http://localhost/nested</h1>, { title: 'Nested2' }))
app2.route('/nested2', app3)
app.route('/nested', app2)
let res = await app.request('http://localhost/nested')
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.text()).toBe(
'<html><head>Nested</head><body><div class="nested"><h1>http://localhost/nested</h1></div></body></html>'
)
res = await app.request('http://localhost/nested/nested2')
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.text()).toBe(
'<html><head>Nested2</head><body><div class="nested"><div class="nested2"><h1>http://localhost/nested</h1></div></div></body></html>'
)
})
it('Should return a default doctype', async () => {
const app = new Hono()
app.use(

View File

@ -2013,7 +2013,7 @@ __metadata:
"@types/react": "npm:^18"
"@types/react-dom": "npm:^18.2.17"
esbuild: "npm:^0.20.2"
hono: "npm:^3.11.7"
hono: "npm:^4.2.3"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
tsup: "npm:^8.0.1"
@ -9661,6 +9661,13 @@ __metadata:
languageName: node
linkType: hard
"hono@npm:^4.2.3":
version: 4.2.3
resolution: "hono@npm:4.2.3"
checksum: eced562fcfc97cd4f2fd4fb081e99775d23c58c8fb1a6c57311ddfba71c23e1da4d33294b0ab1fdd0c4587351dc278f4f1f7d02fcd0ded156c32c74e66a01f45
languageName: node
linkType: hard
"hosted-git-info@npm:^2.1.4":
version: 2.8.9
resolution: "hosted-git-info@npm:2.8.9"