diff --git a/.changeset/chilly-pumas-hear.md b/.changeset/chilly-pumas-hear.md new file mode 100644 index 00000000..7a36eafa --- /dev/null +++ b/.changeset/chilly-pumas-hear.md @@ -0,0 +1,5 @@ +--- +'@hono/react-renderer': minor +--- + +nested layout support for react-renderer diff --git a/packages/react-renderer/package.json b/packages/react-renderer/package.json index 0690ddaa..e5317413 100644 --- a/packages/react-renderer/package.json +++ b/packages/react-renderer/package.json @@ -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", diff --git a/packages/react-renderer/src/react-renderer.ts b/packages/react-renderer/src/react-renderer.ts index e83bf916..fa58e5d5 100644 --- a/packages/react-renderer/src/react-renderer.ts +++ b/packages/react-renderer/src/react-renderer.ts @@ -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 & { children: React.ReactElement }> } + const RequestContext = React.createContext(null) const createRenderer = - (c: Context, component?: React.FC, options?: RendererOptions) => + ( + c: Context, + Layout: React.FC<{ children: React.ReactElement }>, + component?: React.FC, + 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, + component?: React.FC, 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() } diff --git a/packages/react-renderer/test/react-renderer.test.tsx b/packages/react-renderer/test/react-renderer.test.tsx index 048b8df4..07b42926 100644 --- a/packages/react-renderer/test/react-renderer.test.tsx +++ b/packages/react-renderer/test/react-renderer.test.tsx @@ -57,6 +57,69 @@ describe('Basic', () => { expect(await res.text()).toBe('

http://localhost/

') }) + it('nested layout with Layout', async () => { + const app = new Hono() + app.use( + '*', + // @ts-expect-error - `title` is not defined + reactRenderer(({ children, title, Layout }) => { + return ( + + + {title} + {children} + + + ) + }) + ) + + const app2 = new Hono() + app2.use( + '*', + // @ts-expect-error - `title` is not defined + reactRenderer(({ children, Layout, title }) => { + return ( + +
{children}
+
+ ) + }) + ) + app2.get('/', (c) => c.render(

http://localhost/nested

, { title: 'Nested' })) + + const app3 = new Hono() + app3.use( + '*', + // @ts-expect-error - `title` is not defined + reactRenderer(({ children, Layout, title }) => { + return ( + +
{children}
+
+ ) + }) + ) + app3.get('/', (c) => c.render(

http://localhost/nested

, { 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( + 'Nested

http://localhost/nested

' + ) + + res = await app.request('http://localhost/nested/nested2') + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe( + 'Nested2

http://localhost/nested

' + ) + }) + it('Should return a default doctype', async () => { const app = new Hono() app.use( diff --git a/yarn.lock b/yarn.lock index 35a0ce90..d6dc0062 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"