Skip to content

Commit

Permalink
types-plugin: enahnce type checking on interface params (vercel#71059)
Browse files Browse the repository at this point in the history
### What

This PR enhances the next-types-plugin to generate more compatible types for cases like typed `params` with a `interface` type `page.tsx` or `layout.tsx`

#### Issue
Previously users might break with type check

```tsx
interface Params { slug: string } // this will fail

export default async function Page({ params }: { params: Promise<Params>})
```
To workaround users have to turn it into a more general `Record` like type

```diff
interface Params {
  slug: string
+ [key: string]: string
}
```

This PR helps get rid of the above workaround. You just need to type it properly with Promise for `params` then it's all done.
  • Loading branch information
huozhi authored Oct 9, 2024
1 parent 2c05914 commit 5f7c780
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ ${
type TEntry = typeof import('${relativePath}.js')
type SegmentParams<T extends Object = any> = T extends Record<string, any>
? { [K in keyof T]: T[K] extends string ? string | string[] | undefined : never }
: T
// Check that the entry is a valid entry
checkFields<Diff<{
${
Expand Down Expand Up @@ -163,7 +167,6 @@ if ('generateStaticParams' in entry) {
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
}
type SegmentParams = {[param: string]: string | string[] | undefined}
export interface PageProps {
params?: Promise<SegmentParams>
searchParams?: Promise<any>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface Params {
slug: string
}

export default async function Page({ params }: { params: Promise<Params> }) {
const paramsValue = await params
return <p>slug:{paramsValue.slug}</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <p>hello world</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { nextTestSetup } from 'e2e-utils'

// This next-types-plugin feature only works in webpack
;(process.env.TURBOPACK ? describe.skip : describe)(
'app-dir - sync-params-type-check',
() => {
const { next } = nextTestSetup({
files: __dirname,
skipStart: true,
skipDeployment: true,
})

it('should pass build with Promise params', async () => {
const { exitCode } = await next.build()
expect(exitCode).toBe(0)
})

it('should fail build with sync params', async () => {
await next.patchFile(
'app/blog/[slug]/page.tsx',
`
interface Params { slug: string }
export default function Page({ params }: { params: Params }) {
return <p>slug:{params.slug}</p>
}
`
)
const { exitCode, cliOutput } = await next.build()
expect(exitCode).toBe(1)
expect(cliOutput).toMatch(
/Type error: Type '{ params: Params; }' does not satisfy the constraint 'PageProps'/
)
})
}
)

0 comments on commit 5f7c780

Please sign in to comment.