diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx
index 293360ad2146..9c2723e8ec50 100644
--- a/packages/next/server/app-render.tsx
+++ b/packages/next/server/app-render.tsx
@@ -375,7 +375,7 @@ function useFlightResponse(
* This is only used for renderToHTML, the Flight response does not need additional wrappers.
*/
function createServerComponentRenderer(
- ComponentToRender: React.ComponentType,
+ ComponentToRender: any,
ComponentMod: {
renderToReadableStream: any
__next_app_webpack_require__?: any
@@ -1442,21 +1442,21 @@ export async function renderToHTMLOrFlight(
isPrefetch && !Boolean(loaderTreeToFilter[2].loading)
? null
: // Create component tree using the slice of the loaderTree
- React.createElement(
- (
- await createComponentTree(
- // This ensures flightRouterPath is valid and filters down the tree
- {
- createSegmentPath: (child) => {
- return createSegmentPath(child)
- },
- loaderTree: loaderTreeToFilter,
- parentParams: currentParams,
- firstItem: isFirst,
- }
- )
- ).Component
- ),
+ // @ts-expect-error TODO-APP: fix async component type
+ React.createElement(async () => {
+ const { Component } = await createComponentTree(
+ // This ensures flightRouterPath is valid and filters down the tree
+ {
+ createSegmentPath: (child) => {
+ return createSegmentPath(child)
+ },
+ loaderTree: loaderTreeToFilter,
+ parentParams: currentParams,
+ firstItem: isFirst,
+ }
+ )
+ return
{val1}
+{val2}
+ > + ) +} diff --git a/test/e2e/app-dir/app/app/react-cache/page.js b/test/e2e/app-dir/app/app/react-cache/page.js new file mode 100644 index 000000000000..14049016a7ff --- /dev/null +++ b/test/e2e/app-dir/app/app/react-cache/page.js @@ -0,0 +1,17 @@ +import Link from 'next/link' +export default function Page() { + return ( + <> ++ + To Server Component + +
++ + To Client Component + +
+ > + ) +} diff --git a/test/e2e/app-dir/app/app/react-cache/server-component/page.js b/test/e2e/app-dir/app/app/react-cache/server-component/page.js new file mode 100644 index 000000000000..94ecf7429b0b --- /dev/null +++ b/test/e2e/app-dir/app/app/react-cache/server-component/page.js @@ -0,0 +1,15 @@ +import { cache } from 'react' + +const getRandomMemoized = cache(() => Math.random()) + +export default function Page() { + const val1 = getRandomMemoized() + const val2 = getRandomMemoized() + return ( + <> +{val1}
+{val2}
+ > + ) +} diff --git a/test/e2e/app-dir/app/app/react-fetch/page.js b/test/e2e/app-dir/app/app/react-fetch/page.js new file mode 100644 index 000000000000..59e6d2a8bd6f --- /dev/null +++ b/test/e2e/app-dir/app/app/react-fetch/page.js @@ -0,0 +1,17 @@ +import Link from 'next/link' +export default function Page() { + return ( + <> ++ + To Server Component + +
++ + To Client Component + +
+ > + ) +} diff --git a/test/e2e/app-dir/app/app/react-fetch/server-component/page.js b/test/e2e/app-dir/app/app/react-fetch/server-component/page.js new file mode 100644 index 000000000000..a1ae02f41e37 --- /dev/null +++ b/test/e2e/app-dir/app/app/react-fetch/server-component/page.js @@ -0,0 +1,18 @@ +async function getRandomMemoizedByFetch() { + const res = await fetch( + 'https://next-data-api-endpoint.vercel.app/api/random' + ) + return res.text() +} + +export default async function Page() { + const val1 = await getRandomMemoizedByFetch() + const val2 = await getRandomMemoizedByFetch() + return ( + <> +{val1}
+{val2}
+ > + ) +} diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index af5fa3044f75..b6ee34e452ee 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -2165,6 +2165,99 @@ describe('app dir', () => { }) describe('known bugs', () => { + describe('should support React cache', () => { + it('server component', async () => { + const browser = await webdriver( + next.url, + '/react-cache/server-component' + ) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + + it('server component client-navigation', async () => { + const browser = await webdriver(next.url, '/react-cache') + + await browser + .elementByCss('#to-server-component') + .click() + .waitForElementByCss('#value-1', 10000) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + + it('client component', async () => { + const browser = await webdriver( + next.url, + '/react-cache/client-component' + ) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + + it('client component client-navigation', async () => { + const browser = await webdriver(next.url, '/react-cache') + + await browser + .elementByCss('#to-client-component') + .click() + .waitForElementByCss('#value-1', 10000) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + }) + + describe('should support React fetch instrumentation', () => { + it('server component', async () => { + const browser = await webdriver( + next.url, + '/react-fetch/server-component' + ) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + + it('server component client-navigation', async () => { + const browser = await webdriver(next.url, '/react-fetch') + + await browser + .elementByCss('#to-server-component') + .click() + .waitForElementByCss('#value-1', 10000) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + + // TODO-APP: React doesn't have fetch deduping for client components yet. + it.skip('client component', async () => { + const browser = await webdriver( + next.url, + '/react-fetch/client-component' + ) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + + // TODO-APP: React doesn't have fetch deduping for client components yet. + it.skip('client component client-navigation', async () => { + const browser = await webdriver(next.url, '/react-fetch') + + await browser + .elementByCss('#to-client-component') + .click() + .waitForElementByCss('#value-1', 10000) + const val1 = await browser.elementByCss('#value-1').text() + const val2 = await browser.elementByCss('#value-2').text() + expect(val1).toBe(val2) + }) + }) it('should not share flight data between requests', async () => { const fetches = await Promise.all( [...new Array(5)].map(() => diff --git a/test/e2e/app-dir/rsc-errors.test.ts b/test/e2e/app-dir/rsc-errors.test.ts index e007b8b44798..5c8decd205b3 100644 --- a/test/e2e/app-dir/rsc-errors.test.ts +++ b/test/e2e/app-dir/rsc-errors.test.ts @@ -104,7 +104,7 @@ describe('app dir - rsc errors', () => { '/server-with-errors/page-export' ) expect(html).toContain( - 'The default export is not a React Component in page: \\"/server-with-errors/page-export\\"' + 'The default export is not a React Component in page:' ) }) })