0

I keep getting could be instantiated with a different subtype of constraint error when I return result that should fit the expected type of a function, and I can't understand what I'm doing wrong. Here's my code, all irrelevant details removed:

type User = { }

interface BasePageProps {
    user: User
}

type GetServerSidePropsResult<P> =
  | { props: P }

type PropsWithUser = <TProps extends BasePageProps>(
    callback: (user: User) => Promise<Omit<TProps, 'user'>>
) => Promise<GetServerSidePropsResult<TProps>>

export const testFunc: PropsWithUser = async (callback) => {
    const user = {}
    return { props: {
            user,
            ...(await callback(user))
        } }
}

And here's the error I'm getting:

'{ user: {}; } & Omit<TProps, "user">' is assignable to the constraint of type 'TProps', but 'TProps' could be instantiated with a different subtype of constraint 'BasePageProps'.

Here's this example on TS Playground.

Why is the potential TProps that was instantiated with a different subtype a problem? How can I fix it?

P.S.: You may notice that these types are taken from Next.js. However, this problem has nothing to do with Next.js itself, so please don't it's tag to this question.

1 Answer 1

1

Your function's valid signature is:

type PropsWithUser = <TProps extends BasePageProps>(
    callback: (user: User) => Promise<Omit<TProps, 'user'>>
) => Promise<GetServerSidePropsResult<BasePageProps & Omit<TProps, 'user'>>>

or you can remove some repetition:

type PropsWithUser = <TProps extends BasePageProps, TO = Omit<TProps, 'user'>(
    callback: (user: User) => Promise<TO>
) => Promise<GetServerSidePropsResult<BasePageProps & TO>>

A counter-example to prove the compiler is right to raise the error about your code. Let's define a concrete example of TProps type that extends BasePageProps but doesn't pass function's result typecheck:

type User = { }

type AnotherUser = { a: string }

interface TProps {
    user: AnotherUser
}

interface BasePageProps {
    user: User
}

type Eq = TProps extends BasePageProps ? true : false // type Eq = true

type PropsWithUser = <TProps extends BasePageProps>(
    callback: (user: User) => Promise<Omit<TProps, 'user'>>
) => Promise<GetServerSidePropsResult<TProps>>

TS playground

So your function should return the result with type Promise<{ props: { user: AnotherUser, ... } }>. That it clearly does not.

Just as the error says: '{ user: {}; } & Omit<TProps, "user">' is assignable to the constraint of type 'TProps', but 'TProps' could be instantiated with a different subtype of constraint 'BasePageProps'

type User = { }

interface BasePageProps {
    user: User
}

type AnotherUser = { a: string }

interface ConcreteTProps {
    user: AnotherUser
}

// { user: {}; } & Omit<TProps, "user"> is assignable
// to the constraint of type TProps (i.e. BasePageProps)
const a: BasePageProps = { user: {} }

// but 'TProps' could be instantiated with a different 
// subtype of constraint 'BasePageProps' (i.e. ConcreteTProps)
// Which is a subtype of BasePageProps by cannot be 
// assigned with { user: User }
const b: ConcreteTProps = { user: {} } // error
Sign up to request clarification or add additional context in comments.

1 Comment

I didn't realise that the type of a field could be also extended on a extended interface, thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.