Next.js: Typescript: NextPage should extend NextComponentType

Created on 27 Nov 2019  路  11Comments  路  Source: vercel/next.js

Next.js export this:

export type NextPage<P = {}, IP = P> = {
  (props: P): JSX.Element | null
  defaultProps?: Partial<P>
  displayName?: string
  getInitialProps?(ctx: NextPageContext): Promise<IP>
}

But also this:

export declare type NextComponentType<C extends BaseContext = NextPageContext, IP = {}, P = {}> = ComponentType<P> & {
    getInitialProps?(context: C): IP | Promise<IP>;
};

Is there a reason why NextPage does not implement NextComponentType? I get a painful Typescript error because of that.

Most helpful comment

I ended up defining a type like this

export type PageWithLayout = NextPage & {
    useLayout: PageLayout;
};

and if my page defines useLayout / getLayout as per this thread i will define them as

(Home as PageWithLayout).useLayout = (page) => ..... //wrap your componenet with the layout you want.

and in my _app.tsx i did this

const MyApp = ({ Component, pageProps, router }: AppProps) => {
    const useLayout = (Component as PageWithLayout).useLayout || ((page) => <DefaultLayout>{page}</DefaultLayout>);
    return useLayout(<Component {...pageProps} />);
};

All 11 comments

@fromi Yes, NextComponentType is used internally (and may not always be used to type the usual pages), meanwhile NextPage defines the page used in your app.

Can you share the error you're getting?

Sure! Actually I created a custom _app page to implement persistent but configurable layout, based on this post: https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/

import App from 'next/app'

class MyApp extends App {
  render() {
    const {Component} = this.props // Here Component's type is `NextComponentType`
  }
}

Doing that I started creating a helper method to check if Component was a Page holding a layout:

export function isLayoutPage(page: NextComponentType): page is LayoutPage {
  return (<LayoutPage>page).getLayout !== undefined
}

Then I wanted to reuse "isLayoutPage" in another place, this time on a 'NextPage' typed object. It did not work because NextComponentType and NextPage are not related.

If NextComponentType is meant to be used internally only, then it should not be exposed when _app is overriden I guess :)

Anyway, it is a very minor issue easily fixed, I just wanted to expose the little issue I came by in case there is something you'd like to improve. Next.js is really great!

@fromi I think you're right, using NextComponentType is not a good idea but NextPage should be compatible with it. This is a very edge case but looks like an easy fix 馃憤

@fromi Just checked and the following code works:

const Comp: NextComponentType = () => {
  return null
}
const Comp2: NextPage = Comp

This also works:

const Comp: NextPage = () => {
  return null
}
const Comp2: NextComponentType = Comp

Can you share a code sample where it fails?

My code changed meanwhile, some I can't find what I did wrong 2 days ago.
I experimented a little bit today and everything looks fine. I probably made a mistake, either by declare a function of 'NextComponentType' where 'C' does not extend 'NextPageContext', or by inverting 'P' and 'IP' (as it is not in the same order in NextComponentType and NextPage).

Everything works great actually, sorry for the bad report!

Ok, I know why I had the issue. Here is an example:

import {NextPage} from 'next'
import App from 'next/app'

class MyApp extends App {
  render() {
    const {Component, pageProps} = this.props
    const page: NextPage = Component // Error: type is not assignable
    return <Component {...pageProps}/>
  }
}

For know I fail to understand why it fails in this case... Some help would be most welcome!

Ok, I am making progress: with your first example, Typescript automatically knows your NextComponentType is a FunctionComponent and not a ComponentClass.
So, here is a code sample where it fails:

function test(Comp: NextComponentType<NextPageContext, any, {}>) {
  const page: NextPage = Comp
}

This way, Typescript does not know whether NextComponentType is a FunctionComponent or a ComponentClass. If it were a ComponentClass, then this function is missing: (props: P): JSX.Element | null

Sure! Actually I created a custom _app page to implement persistent but configurable layout, based on this post: https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/

import App from 'next/app'

class MyApp extends App {
  render() {
    const {Component} = this.props // Here Component's type is `NextComponentType`
  }
}

Doing that I started creating a helper method to check if Component was a Page holding a layout:

export function isLayoutPage(page: NextComponentType): page is LayoutPage {
  return (<LayoutPage>page).getLayout !== undefined
}

Then I wanted to reuse "isLayoutPage" in another place, this time on a 'NextPage' typed object. It did not work because NextComponentType and NextPage are not related.

If NextComponentType is meant to be used internally only, then it should not be exposed when _app is overriden I guess :)

Anyway, it is a very minor issue easily fixed, I just wanted to expose the little issue I came by in case there is something you'd like to improve. Next.js is really great!

I was on the same boat and wanted to implement getLayout; do you have a snippet how you did it

I was on the same boat and wanted to implement getLayout; do you have a snippet how you did it

I don't, and anyway it will not be any clearer than Adam Wathan's post, which contains all the snippets you need!

I ended up defining a type like this

export type PageWithLayout = NextPage & {
    useLayout: PageLayout;
};

and if my page defines useLayout / getLayout as per this thread i will define them as

(Home as PageWithLayout).useLayout = (page) => ..... //wrap your componenet with the layout you want.

and in my _app.tsx i did this

const MyApp = ({ Component, pageProps, router }: AppProps) => {
    const useLayout = (Component as PageWithLayout).useLayout || ((page) => <DefaultLayout>{page}</DefaultLayout>);
    return useLayout(<Component {...pageProps} />);
};

Sure! Actually I created a custom _app page to implement persistent but configurable layout, based on this post: https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/

import App from 'next/app'

class MyApp extends App {
  render() {
    const {Component} = this.props // Here Component's type is `NextComponentType`
  }
}

Doing that I started creating a helper method to check if Component was a Page holding a layout:

export function isLayoutPage(page: NextComponentType): page is LayoutPage {
  return (<LayoutPage>page).getLayout !== undefined
}

Then I wanted to reuse "isLayoutPage" in another place, this time on a 'NextPage' typed object. It did not work because NextComponentType and NextPage are not related.

If NextComponentType is meant to be used internally only, then it should not be exposed when _app is overriden I guess :)

Anyway, it is a very minor issue easily fixed, I just wanted to expose the little issue I came by in case there is something you'd like to improve. Next.js is really great!

this might be a bit off topic but I was wondering when
implementing persistent and configurable layout, how did you manage 404's , whenever my pages get 404/500 it refreshes, it is rendered in the layout but it is no more persistent; I was wondering if it was my implementation detail that had something wrong or you faced similar experience ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jesselee34 picture jesselee34  路  3Comments

kenji4569 picture kenji4569  路  3Comments

ghost picture ghost  路  3Comments

pie6k picture pie6k  路  3Comments

renatorib picture renatorib  路  3Comments