Closed lukebarton closed 2 years ago
Next only checks this:
export type InferGetServerSidePropsType<T> = T extends GetServerSideProps<
infer P,
any
>
? P
: T extends (
context?: GetServerSidePropsContext<any>
) => Promise<GetServerSidePropsResult<infer P>>
? P
: never
So if neither the function is typed nor the argument, I think it has no way of knowing if the function is actually of type GetServerSideProps
. I supose changing the above to something like:
export type InferGetServerSidePropsType<T> = T extends GetServerSideProps<
infer P,
any
>
? P
: T extends (
context?: any
) => Promise<GetServerSidePropsResult<infer P>>
? P
: never
would make it infer your type, because the conditon would hold true?
I think you're right, however this one works, where the function isn't typed, and nor is the argument:
export const getServerSideProps = async (ctx) => {
It also happens in cases like this:
export const getServerSideProps = async (ctx) => {
const { userId } = ctx.params
const user = await getUser(userId)
if(user) {
return {
props: { user }
}
} else {
return { notFound: true }
}
};
This also doesn't work:
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
return {
props: { foo: "bar" }
}
};
export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }
or
export const getServerSideProps: GetServerSideProps = async ({ params }: GetServerSidePropsContext) => {
return {
props: { foo: "bar" }
}
};
export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }
But removing the GetServerSideProps
works:
export const getServerSideProps = async ({ params }: GetServerSidePropsContext) => {
return {
props: { foo: "bar" }
}
};
export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }
NextJS: v10.0.1
For me, the magic was to make sure the getServerSideProps
function args are explicitly typed using the GetServerSidePropsContext
type. Otherwise, the page props are inferred as any
type, e.g. user: any
.
I can confirm that @tgallacher bottom sample also works for me.
This is a sneaky one, my string props were mistyped but that didn't even cause a problem with strict TS. It only was an issue when I had an object in the props and wanted to access a property.
Here is my solution, key was to provide return type of getServerSideProps into GetServerSideProps type, here is example: https://gist.github.com/nenadfilipovic/f2dd9cb903da93a7d14ed1de6b3493b1
Setting types like this works:
GetServerSideProps<{
user: User
posts: Post[]
}>
I found out something interessing.
Type inference via InferGetServerSidePropsType<typeof getServerSideProps>
seems to be working as long as I only return props.
return {
props: {
user: {
firstName,
lastName
}
}
};
If i additionally return a redirect or a notFound conditionally type inference stops working for me.
return {
redirect: {
destination: '',
permanent: false,
},
};
return {
notFound: true
};
Setting types like this works:
GetServerSideProps<{ user: User posts: Post[] }>
This also works for me, but it would be more comfortable if it would work without the extra type specification. If the type is created like this, you would not need to infer it, because you can directly use it in the component.
It probably works in first case because return type is { [key: string]: any }
That is default type for props object
Having the same issue. Is there a proper solution? Not quite sure which one to choose in all the previous suggestions.
@binajmen
The easiest solution imo would be by adding a PageProps type to GetServerSideProps
generic. If there is a fix that the type infer starts working without the extra type specification you are already set up. And having your PageProps typed out is not a bad thing either.
type PageProps = {
user: {
firstName: string,
lastName: string
};
};
export const getServerSideProps: GetServerSideProps<PageProps> = async (ctx) => {
return {
props: {
user: {
firstName,
lastName
}
}
};
};
export const Page = ({ user }: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }
Hi @mkreuzmayr
Thanks, sounds like an acceptable solution! However it doesn't work with next-firebase-auth
and I can't figure out how to combine PageProps
with it:
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { AuthAction, useAuthUser, withAuthUser, withAuthUserTokenSSR } from 'next-firebase-auth'
function Page(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
const user = useAuthUser()
return (
<div>Message: {props.message} from {user.id}</div>
)
}
export default withAuthUser<InferGetServerSidePropsType<typeof getServerSideProps>>({
whenUnauthedBeforeInit: AuthAction.SHOW_LOADER,
whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN
})(Page)
export const getServerSideProps: GetServerSideProps<PageProps> = withAuthUserTokenSSR({
whenUnauthed: AuthAction.REDIRECT_TO_LOGIN,
})(async () => {
return { props: { message: "Hello" } }
})
TS error:
Type 'Promise<GetServerSidePropsResult<{ [key: string]: any; }>>' is not assignable to type 'GetServerSideProps<PageProps, ParsedUrlQuery>'.
Type 'Promise<GetServerSidePropsResult<{ [key: string]: any; }>>' provides no match for the signature '(context: GetServerSidePropsContext<ParsedUrlQuery>): Promise<GetServerSidePropsResult<PageProps>>'.ts(2322)
I've found a workaround - first of all - something broke with 11.1.x
as could use InferGetServerSidePropsType<typeof getServerSideProps>
even with notFound
using the below.
Here's my hacky implementation that I just wrote:
utils/inferSSRProps.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
type GetSSRResult<TProps> =
//
{ props: TProps } | { redirect: any } | { notFound: true };
type GetSSRFn<TProps extends any> = (args: any) => Promise<GetSSRResult<TProps>>;
export type inferSSRProps<TFn extends GetSSRFn<any>> = TFn extends GetSSRFn<infer TProps>
? NonNullable<TProps>
: never;
pages/somePage.tsx
import { inferSSRProps } from '../utils/inferSSRProps'
import { GetServerSidePropsContext } from "next";
import prisma from "@lib/prisma";
export default MyPage(props: inferSSRProps<typeof getServerSideProps>) {
// ...
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const post = await prisma.post.findFirst({
where: {
username: (context.query.slug as string).toLowerCase(),
},
select: {
id: true,
// ...
},
});
if (!post) {
return {
notFound: true,
} as const; // <-- important, this needs to be `as const`
}
return {
props: {
post,
},
};
}
Hey @KATT, thanks for your solution!
You do not need to cast { notFound: true }
to const
if you change your GetSSRResult
notFound
type to boolean
.
utils/inferSSRProps.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
type GetSSRResult<TProps> =
{ props: TProps } | { redirect: any } | { notFound: boolean }; // <-------
type GetSSRFn<TProps extends any> = (args: any) => Promise<GetSSRResult<TProps>>;
export type inferSSRProps<TFn extends GetSSRFn<any>> = TFn extends GetSSRFn<infer TProps>
? NonNullable<TProps>
: never;
I solved the issue by publishing my own infer type:
notFound
redirect
getStaticProps
as well as getServerSideProps
npm install infer-next-props-type --save-dev
import InferNextPropsType from 'infer-next-props-type'
export function getStaticProps() {
return {
props: { foo: 'bar' }
}
}
export default function Page(props: InferNextPropsType<typeof getStaticProps>) {
return ...
}
import InferNextPropsType from 'infer-next-props-type'
export function getServerSideProps() {
return {
props: { foo: 'bar' }
}
}
export default function Page(props: InferNextPropsType<typeof getServerSideProps>) {
return ...
}
@HaNdTriX any reason for not updating the built-in type?
Because my type still has some edge cases to cover. Will deprecate the module as soon as we found the perfect working type and push the changes upstream.
Might be interesting https://github.com/microsoft/TypeScript/issues/38511
I found out something interessing. Type inference via
InferGetServerSidePropsType<typeof getServerSideProps>
seems to be working as long as I only return props.return { props: { user: { firstName, lastName } } };
If i additionally return a redirect or a notFound conditionally type inference stops working for me.
return { redirect: { destination: '', permanent: false, }, };
return { notFound: true };
Setting types like this works:
GetServerSideProps<{ user: User posts: Post[] }>
This also works for me, but it would be more comfortable if it would work without the extra type specification. If the type is created like this, you would not need to infer it, because you can directly use it in the component.
returning an empty props in redirect worked for me:
return {
redirect: {
destination: '',
permanent: false,
},
props: {}
}
Thanks @Markyiptw for the hint, that helped me a lot in figuring out what was going on.
The disadvantage of that solution is that the props will all become optional, so that may not always be ideal.
I slapped together this custom type that seems to do work well when using getServerSideProps
that may return a redirect:
export type CustomInferGetServerSidePropsType<T> = T extends (
context?: any
) => Promise<{ props: infer P }>
? P
: T extends (context?: any) => Promise<GetServerSidePropsResult<infer P>>
? P
: never;
I'm sharing this because the other types shared in this issue didn't quite work for my cases.
I found a best work around.
remove the : GetStaticProps
from const getStaticProps = async () => {...}
Then the const SomePage: NextPage<InferGetStaticPropsType<typeof getStaticProps>>= ({/* inferred types */}){}
can work correctly.
Problem was as said here https://github.com/vercel/next.js/issues/32434#issuecomment-993013691. So all after adding the : GetStaticProps
, the return type of getStaticProps would be extended to { [key: string]: any; }
by TS because it includes the original type of {'foo': string}.
Looks like this will hopefully be solved with Typescript 4.9!
I was playing around with the new satisfies keyword and I can certainly see it helping.
We’ll still need to cast the notFound and redirects returns as consts.
I’m also of the opinion that we should expect props to be able to return null unless a type guard is in place, which I’ve also included in the below example.
I have created a PR to solve this issue^^
This issue has been fixed by #40635
Closing per above
This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.
Describe the bug
causes
props: never
however the following works fine:
as does: