0

I created a wrapper for GetServerSideProps SSR function to reduce repetition but I have problem with typing it correctly with TypeScript. Here's the wrapper:

type WithSessionType = <T extends {}>(
  callback: GetServerSideProps<T>
) => GetServerSideProps<{ session: Session | null } & T>

//                                                 v error
const withSession: WithSessionType = (callback) => async (ctx) => {
  const session = await getSession(ctx)
  const result = await callback?.(ctx)
  const props = result && 'props' in result ? result.props : {}
  return {
    ...result,
    props: {
      session,
      ...props,
    },
  }
}

The error:

Type '(ctx: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) => Promise<{ props: { session: Session | null; }; redirect: Redirect; } | { ...; } | { ...; }>' is not assignable to type 'GetServerSideProps<{ session: Session | null; } & T, ParsedUrlQuery, PreviewData>'.
  Type 'Promise<{ props: { session: Session | null; }; redirect: Redirect; } | { props: { session: Session | null; }; notFound: true; } | { props: { session: Session | null; }; }>' is not assignable to type 'Promise<GetServerSidePropsResult<{ session: Session | null; } & T>>'.
    Type '{ props: { session: Session | null; }; redirect: Redirect; } | { props: { session: Session | null; }; notFound: true; } | { props: { session: Session | null; }; }' is not assignable to type 'GetServerSidePropsResult<{ session: Session | null; } & T>'.
      Type '{ props: { session: Session | null; }; }' is not assignable to type 'GetServerSidePropsResult<{ session: Session | null; } & T>'.
        Type '{ props: { session: Session | null; }; }' is not assignable to type '{ props: ({ session: Session | null; } & T) | Promise<{ session: Session | null; } & T>; }'.
          Types of property 'props' are incompatible.
            Type '{ session: Session | null; }' is not assignable to type '({ session: Session | null; } & T) | Promise<{ session: Session | null; } & T>'.
              Type '{ session: Session | null; }' is not assignable to type '{ session: Session | null; } & T'.
                Type '{ session: Session | null; }' is not assignable to type 'T'.
                  '{ session: Session | null; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.ts(2322)

Usecase (that works):

export const getServerSideProps = withSession(async () => {
  return {
    props: {
      custom: 'custom string type',
    },
  }
})

Here is a computed type of getServerSideProps:

const getServerSideProps: GetServerSideProps<{
    session: Session | null;
} & {
    custom: string;
}, ParsedUrlQuery, PreviewData>

It works fine and even InferGetServerSidePropsType infers types correctly but the mentioned error still occurs.

correct auto completion

Any idea how can I make the TypeScript happy here? Also looking forward suggestions and tips regarding this kind of wrapper if you have any.

UPDATE

I found what is the cause but I don't quite understand why is that.. Turns out it treated this statement as always false even though props can exist on this type. enter image description here

GetServerSidePropsResult type:

export type GetServerSidePropsResult<P> =
  | { props: P | Promise<P> }
  | { redirect: Redirect }
  | { notFound: true }

Why is only redirect visible on this type and not props or notFound?

UPDATE 2 - I'm losing my mind

enter image description here

enter image description here Why T only exist on 2nd?

jjablonski
  • 33
  • 1
  • 5

2 Answers2

1

The issue is described in another question: How to fix TS2322: "could be instantiated with a different subtype of constraint 'object'"?

I haven't been able to understand what should be done exactly to make TS happy but I have a workaround which is to cast the returned function instead of declaring the type returned:

function withSession<T extends {}>(callback: GetServerSideProps<T>){
  return (async (ctx) => {
    const session = await getSession(ctx)
    const result = await callback?.(ctx)
    const props = result && 'props' in result ? result.props : {}
    return {
      ...result,
      props: {
        session,
        ...props,
      },
    }
  }) as GetServerSideProps<{ session: Session | null } & T>
}
D0m3
  • 1,471
  • 2
  • 13
  • 19
0

Not quite sure but I think the problem is that T could have another definition of session and so the type is not assignable anymore. You could just use Omit<T,"session"> to exclude session from T or you could write something like <T extends { session?: Session | null > instead of <T extends {}>

Btw. {} In TS in practice means "any non-null value" so you should consider using something like Record<string| number, any> instead

( Not tested, written on my phone so there may be mistakes )