Skip to content

Commit

Permalink
feat!: support latest Next.js versions, fix server-only props being p…
Browse files Browse the repository at this point in the history
…assed to Client Document Views (#7026)

**BREAKING:** The minimum required Next.js version has been bumped from
`15.0.0-rc.0` to `15.0.0-canary.53`. This is because the way client
components are represented changed somewhere between those versions, and
it is not feasible to support both versions in our RSC detection logic.
  • Loading branch information
AlessioGr authored and paulpopus committed Jul 24, 2024
1 parent 469dc87 commit 6a9be9f
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 101 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
"lint-staged": "^14.0.1",
"minimist": "1.2.8",
"mongodb-memory-server": "^9.0",
"next": "15.0.0-rc.0",
"next": "15.0.0-canary.53",
"open": "^10.1.0",
"p-limit": "^5.0.0",
"pino": "8.15.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
},
"peerDependencies": {
"graphql": "^16.8.1",
"next": "^15.0.0-rc.0",
"next": "^15.0.0-canary.53",
"payload": "workspace:*"
},
"engines": {
Expand Down
10 changes: 2 additions & 8 deletions packages/next/src/views/Document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,6 @@ export const Document: React.FC<AdminViewProps> = async ({
}
}

const viewComponentProps: ServerSideEditViewProps = {
initPageResult,
params,
routeSegments: segments,
searchParams,
}

return (
<DocumentInfoProvider
action={action}
Expand Down Expand Up @@ -246,13 +239,14 @@ export const Document: React.FC<AdminViewProps> = async ({
<RenderCustomComponent
CustomComponent={ViewOverride || CustomView}
DefaultComponent={DefaultView}
componentProps={viewComponentProps}
serverOnlyProps={{
i18n,
initPageResult,
locale,
params,
payload,
permissions,
routeSegments: segments,
searchParams,
user,
}}
Expand Down
42 changes: 13 additions & 29 deletions packages/payload/src/utilities/isReactComponent.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,31 @@
import type React from 'react'

import { isPlainObject } from './isPlainObject.js'

/*
For reference: console.log output of [ClientComponent, RSC] array, tested in Turbo and Webpack (14.3.0-canary.37)
Both component functions async:
Turbo: [ [Function (anonymous)], [AsyncFunction: ExampleServer] ]
Webpack: [ {}, [AsyncFunction: ExampleServer] ]
Both component functions non-async:
Turbo: [ [Function (anonymous)], [Function: ExampleServer] ]
Webpack: [ {}, [Function: ExampleServer] ]
*/
const clientRefSymbol = Symbol.for('react.client.reference')

export function isReactServerComponentOrFunction<T extends any>(
component: React.ComponentType | any,
): component is T {
const isClassComponent =
typeof component === 'function' &&
component.prototype &&
typeof component.prototype.render === 'function'

const isFunctionalComponent =
typeof component === 'function' && (!component.prototype || !component.prototype.render)
if (component === null || component === undefined) {
return false
}
const hasClientComponentSymbol = component.$$typeof == clientRefSymbol

const isFunctionalComponent = typeof component === 'function'
// Anonymous functions are Client Components in Turbopack. RSCs should have a name
const isAnonymousFunction = typeof component === 'function' && component.name === ''

return (isClassComponent || isFunctionalComponent) && !isAnonymousFunction
const isRSC = isFunctionalComponent && !isAnonymousFunction && !hasClientComponentSymbol

return isRSC
}

export function isReactClientComponent<T extends any>(
component: React.ComponentType | any,
): component is T {
const isClientComponentWebpack = typeof component === 'object' && !isPlainObject(component) // In Webpack, client components are {}
const isClientComponentTurbo = typeof component === 'function' && component.name === '' // Anonymous functions are Client Components in Turbopack

return isClientComponentWebpack || isClientComponentTurbo
if (component === null || component === undefined) {
return false
}
return !isReactServerComponentOrFunction(component) && component.$$typeof == clientRefSymbol
}

export function isReactComponentOrFunction<T extends any>(
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
"turbopack": "^0.0.1"
},
"peerDependencies": {
"next": "^15.0.0-rc.0",
"next": "^15.0.0-canary.53",
"payload": "workspace:*",
"react": "^19.0.0 || ^19.0.0-rc-f994737d14-20240522",
"react-dom": "^19.0.0 || ^19.0.0-rc-f994737d14-20240522"
Expand Down
116 changes: 60 additions & 56 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6a9be9f

Please sign in to comment.