fix: InferGetServerSidePropsType and InferGetStaticPropsType (#40635)
## Problem Currently the Next.js infer utility (`InferGetServerSidePropsType` and `InferGetStaticPropsType`) types can lead to a wrong inferred types (`never`). This happens if these functions return something different than: `{props: {}}`. **Example:** `getServerSideProps` ```typescript export async function getServerSideProps({ query }: GetServerSidePropsContext) { if (query.foo) { return { notFound: true, } } return { props: { foo: "bar" }, } } type PageProps = InferGetServerSidePropsType<typeof getServerSideProps> // => type PageProps = never ``` **Example:** `getStaticProps` ```typescript import type { InferGetStaticPropsType, GetStaticPropsContext } from 'next' export async function getStaticProps(context: GetStaticPropsContext) { if (context.params?.bar) { return { notFound: true, } } return { props: { foo: 'bar', }, } } type PageProps = InferGetStaticPropsType<typeof getStaticProps> // => type PageProps = never ``` This is because the first infer condition of the utility type is not satified leading to a never result. ```typescript export type InferGetServerSidePropsType<T> = T extends GetServerSideProps< infer P, // <- NOT SATISFIED any > ? P : T extends ( context?: GetServerSidePropsContext<any> ) => Promise<GetServerSidePropsResult<infer P>> ? P : never // <- NOT SATISFIED ``` ## Solution I have experimented with different solutions ending with a much simpler type, that is faster to execute, easier to read and universally usable for both prop variations. ```typescript /** * Flow: * - Make sure getStaticProps is a function * - Get its return type * - Extract the one that contains {props: any} * - Return the props */ export type InferGetStaticPropsType<T extends (args: any) => any> = Extract< Awaited<ReturnType<T>>, { props: any } >['props'] ``` ## Bug - [x] Related issues: fixes #36615, #15913, https://twitter.com/leeerob/status/1563540593003106306 - [x] Type tests added ## Future thoughts Since `InferGetStaticPropsType` and `InferGetServerSidePropsType` are now the same, it's api could be merged into one utility type (e.g: InferNextProps). I recommend doing this in a different PR. ## Additional info I have tested this approach using the following [external package](https://www.npmjs.com/package/infer-next-props-type) (@timneutkens sorry for the late PR). Since about 12 Month I haven't received any negative feedback (issues) regarding this approach. Co-authored-by: JJ Kasper <jj@jjsweb.site>
This commit is contained in:
parent
c2f48ea86d
commit
3943b20f55
5 changed files with 137 additions and 16 deletions
|
@ -135,6 +135,7 @@
|
|||
"eslint-plugin-react-hooks": "4.5.0",
|
||||
"event-stream": "4.0.1",
|
||||
"execa": "2.0.3",
|
||||
"expect-type": "0.14.2",
|
||||
"express": "4.17.0",
|
||||
"faker": "5.5.3",
|
||||
"faunadb": "2.6.1",
|
||||
|
|
22
packages/next/types/index.d.ts
vendored
22
packages/next/types/index.d.ts
vendored
|
@ -128,13 +128,10 @@ export type GetStaticProps<
|
|||
context: GetStaticPropsContext<Q, D>
|
||||
) => Promise<GetStaticPropsResult<P>> | GetStaticPropsResult<P>
|
||||
|
||||
export type InferGetStaticPropsType<T> = T extends GetStaticProps<infer P, any>
|
||||
? P
|
||||
: T extends (
|
||||
context?: GetStaticPropsContext<any>
|
||||
) => Promise<GetStaticPropsResult<infer P>> | GetStaticPropsResult<infer P>
|
||||
? P
|
||||
: never
|
||||
export type InferGetStaticPropsType<T extends (args: any) => any> = Extract<
|
||||
Awaited<ReturnType<T>>,
|
||||
{ props: any }
|
||||
>['props']
|
||||
|
||||
export type GetStaticPathsContext = {
|
||||
locales?: string[]
|
||||
|
@ -181,16 +178,9 @@ export type GetServerSideProps<
|
|||
context: GetServerSidePropsContext<Q, D>
|
||||
) => Promise<GetServerSidePropsResult<P>>
|
||||
|
||||
export type InferGetServerSidePropsType<T> = T extends GetServerSideProps<
|
||||
infer P,
|
||||
any
|
||||
export type InferGetServerSidePropsType<T extends (args: any) => any> = Awaited<
|
||||
Extract<Awaited<ReturnType<T>>, { props: any }>['props']
|
||||
>
|
||||
? P
|
||||
: T extends (
|
||||
context?: GetServerSidePropsContext<any>
|
||||
) => Promise<GetServerSidePropsResult<infer P>>
|
||||
? P
|
||||
: never
|
||||
|
||||
declare global {
|
||||
interface Crypto {
|
||||
|
|
|
@ -96,6 +96,7 @@ importers:
|
|||
eslint-plugin-react-hooks: 4.5.0
|
||||
event-stream: 4.0.1
|
||||
execa: 2.0.3
|
||||
expect-type: 0.14.2
|
||||
express: 4.17.0
|
||||
faker: 5.5.3
|
||||
faunadb: 2.6.1
|
||||
|
@ -251,6 +252,7 @@ importers:
|
|||
eslint-plugin-react-hooks: 4.5.0_eslint@7.24.0
|
||||
event-stream: 4.0.1
|
||||
execa: 2.0.3
|
||||
expect-type: 0.14.2
|
||||
express: 4.17.0
|
||||
faker: 5.5.3
|
||||
faunadb: 2.6.1
|
||||
|
@ -14088,6 +14090,13 @@ packages:
|
|||
homedir-polyfill: 1.0.3
|
||||
dev: true
|
||||
|
||||
/expect-type/0.14.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ed3+tr5ujbIYXZ8Pl/VgIphwJQ0q5tBLGGdn7Zvwt1WyPBRX83xjT5pT77P/GkuQbctx0K2ZNSSan7eruJqTCQ==,
|
||||
}
|
||||
dev: true
|
||||
|
||||
/expect/26.6.2:
|
||||
resolution:
|
||||
{
|
||||
|
|
69
test/unit/infer-get-server-side-props-type.test.ts
Normal file
69
test/unit/infer-get-server-side-props-type.test.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import type {
|
||||
InferGetServerSidePropsType,
|
||||
GetServerSidePropsContext,
|
||||
} from 'next'
|
||||
import { expectTypeOf } from 'expect-type'
|
||||
|
||||
describe('InferGetServerSidePropsType', () => {
|
||||
it('should work with sync functions', async () => {
|
||||
function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
if (context.params?.notFound) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
foo: 'bar',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type PageProps = InferGetServerSidePropsType<typeof getServerSideProps>
|
||||
|
||||
expectTypeOf<PageProps>().toEqualTypeOf<{ foo: string }>()
|
||||
})
|
||||
|
||||
it('should work with async functions', async () => {
|
||||
async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
if (context.params?.notFound) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (context.params?.redirect) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
foo: 'bar',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type PageProps = InferGetServerSidePropsType<typeof getServerSideProps>
|
||||
|
||||
expectTypeOf<PageProps>().toEqualTypeOf<{ foo: string }>()
|
||||
})
|
||||
|
||||
it('should work with promised props', async () => {
|
||||
async function getServerSideProps() {
|
||||
return {
|
||||
props: Promise.resolve({
|
||||
foo: 'bar',
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
type PageProps = InferGetServerSidePropsType<typeof getServerSideProps>
|
||||
|
||||
expectTypeOf<PageProps>().toEqualTypeOf<{ foo: string }>()
|
||||
})
|
||||
})
|
52
test/unit/infer-get-static-props.test.ts
Normal file
52
test/unit/infer-get-static-props.test.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import type { InferGetStaticPropsType, GetStaticPropsContext } from 'next'
|
||||
import { expectTypeOf } from 'expect-type'
|
||||
|
||||
describe('InferGetServerSidePropsType', () => {
|
||||
it('should work with sync functions', async () => {
|
||||
function getStaticProps(context: GetStaticPropsContext) {
|
||||
if (context.params?.notFound) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
foo: 'bar',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type PageProps = InferGetStaticPropsType<typeof getStaticProps>
|
||||
|
||||
expectTypeOf<PageProps>().toEqualTypeOf<{ foo: string }>()
|
||||
})
|
||||
|
||||
it('should work with async functions', async () => {
|
||||
async function getStaticProps(context: GetStaticPropsContext) {
|
||||
if (context.params?.notFound) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (context.params?.redirect) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
foo: 'bar',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type PageProps = InferGetStaticPropsType<typeof getStaticProps>
|
||||
|
||||
expectTypeOf<PageProps>().toEqualTypeOf<{ foo: string }>()
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue