Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

next/app as functional component #7515

Closed
dmitrizzle opened this issue Jun 5, 2019 · 28 comments · Fixed by #9268
Closed

next/app as functional component #7515

dmitrizzle opened this issue Jun 5, 2019 · 28 comments · Fixed by #9268
Milestone

Comments

@dmitrizzle
Copy link

Feature request

Problem

Functional components yield a more performant and readable code. It's been hinted by React team that class'es may be split into a separate package in the near future. It would be nice for Next.js to provide a way to create apps without classes. Currently it's not possible as _app.js and _document.js require to extend Next's React classes.

Solution

If Next's controlling components can be rewritten with React Hooks, the entire Next app should consist of functional components by default. Perhaps we could have a version that uses that style as an opt-in for backwards compatibility.

Alternative

I am considering rewriting the above-mentioned components in my project folder, but I fear that it may cause some issues.

Additional context

This is pretty much it.

@Timer Timer modified the milestones: 9.x, 9.0.x Jul 18, 2019
@tsaiDavid
Copy link

This would be great! Would love to be able to use hooks in _app.tsx!

@Timer Timer modified the milestones: 9.0.x, backlog Aug 30, 2019
@medmin

This comment has been minimized.

@timneutkens
Copy link
Member

timneutkens commented Sep 23, 2019

@medmin please don't spam issues.

You can already use hooks in _app.js by doing:

import React from 'react'
import App from 'next/app'

function MyComponent({children}) {
  // You can use hooks here
  return <>{children}</> // The fragment is just illustrational
}

class MyApp extends App {
  // Only uncomment this method if you have blocking data requirements for
  // every single page in your application. This disables the ability to
  // perform automatic static optimization, causing every page in your app to
  // be server-side rendered.
  //
  // static async getInitialProps(appContext) {
  //   // calls page's `getInitialProps` and fills `appProps.pageProps`
  //   const appProps = await App.getInitialProps(appContext);
  //
  //   return { ...appProps }
  // }

  render() {
    const { Component, pageProps } = this.props
    return <MyComponent>
      <Component {...pageProps} />
    </MyComponent>
  }
}

export default MyApp

We've already made changes to Next.js to allow the exported component itself to be a functional component. However this would break withRouter as that still uses legacy context for backwards compat reasons.

@dmitrizzle
Copy link
Author

@timneutkens do you mean It'll break withRouter wherever it's being used, or just with _app.js?

@timneutkens
Copy link
Member

timneutkens commented Sep 23, 2019

Whenever it's being used. Mostly related to Apollo and it's tree traversing (if you're using Apollo). Hence why we can't get rid of the extending of the original next/app yet.

@mikestopcontinues
Copy link

@timneutkens Can you expand on the issue? Or point to somewhere it's discussed? Also, what's the plan to move forward?

@timneutkens
Copy link
Member

@mikestopcontinues it's been solved since my last comment and the latest release of Next.js (9.1.2). I've opened a PR to update the docs.

@mikestopcontinues
Copy link

Very cool. Thanks!

@Timer Timer modified the milestones: backlog, 9.1.3 Oct 31, 2019
@mvllow
Copy link
Contributor

mvllow commented Nov 1, 2019

Side note, it appears dynamic routing does not provide a query to getInitialProps when using this functional app. If this is not a known bug I can open a new issue with more details.

@tsujp
Copy link

tsujp commented Dec 11, 2019

This issue is closed and marked resolved but there were only changes to _app.tsx, where are the changes in documentation and examples for _document.tsx?

Is it this?

import Document, { Html, Head, Main, NextScript } from 'next/document'
import { AppInitialProps } from 'next/app'

const AppDocument = ({ ...initialProps }: Document & AppInitialProps) => {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

export default AppDocument

@timneutkens
Copy link
Member

Document should be a class extending from next/document as documented. This issue is completely unrelated to _document.

@amalv
Copy link

amalv commented Dec 18, 2019

I've seen the documentation has been updated in this PR that was merged in October 31, but I can't see it yet in the official documentation

By the way, which should be the types for _app.tsx?

I'm currently using this:

interface MyAppProps {
  Component: React.FC;
  pageProps: any;
}
const MyApp = ({ Component, pageProps }: MyAppProps): JSX.Element => {
  ...
}

Which seems to work fine, but I'm not sure if this is the best approach, particularly regarding pageProps since I'm using a any type.

@0xedb

This comment has been minimized.

@timneutkens
Copy link
Member

timneutkens commented Dec 18, 2019

@theBashShell as said, extend from next/document, don't do what you posted as I can guarantee it will break 100% in any future update. Hid the comment for that reason.

@MacZel
Copy link

MacZel commented Dec 18, 2019

Which seems to work fine, but I'm not sure if this is the best approach, particularly regarding pageProps since I'm using a any type.

you can easily type it like this:

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

const App: NextPage<AppProps> = ({ Component, pageProps }) => {
}

@miloofcroton
Copy link

miloofcroton commented Feb 24, 2020

you can easily type it like this:

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

const App: NextPage<AppProps> = ({ Component, pageProps }) => {
}

I don't think this is right. NextPage is not the right type for _app. The signature for getInitialProps differs. I need Component and ctx off the parameter sent to getInitialProps for _app, whereas other pages (not _app) get just that ctx property itself as the paramter.

I'm leaving it untyped at the moment until I find the right type.

For reference,

this is NextPage:

export type NextPage<P = {}, IP = P> = {
  (props: P): JSX.Element | null
  defaultProps?: Partial<P>
  displayName?: string
  /**
   * Used for initial page load data population. Data returned from `getInitialProps` is serialized when server rendered.
   * Make sure to return plain `Object` without using `Date`, `Map`, `Set`.
   * @param ctx Context of `page`
   */
  getInitialProps?(ctx: NextPageContext): Promise<IP>
}

and the getInitialProps that I need is in here:

declare function appGetInitialProps({ Component, ctx, }: AppContext): Promise<AppInitialProps>;
export default class App<P = {}, CP = {}, S = {}> extends React.Component<P & AppProps<CP>, S> {
    static origGetInitialProps: typeof appGetInitialProps;
    static getInitialProps: typeof appGetInitialProps;
    componentDidCatch(error: Error, _errorInfo: ErrorInfo): void;
    render(): JSX.Element;
}

@myeongjae-kim
Copy link

myeongjae-kim commented Apr 7, 2020

Here is the perfect _app type.

// import App from "next/app";
import { NextComponentType } from "next"
import { AppContext, AppInitialProps, AppProps } from "next/app";

const MyApp: NextComponentType<AppContext, AppInitialProps, AppProps> = ({ Component, pageProps }) => {
  return <Component {...pageProps} />
}

// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.

// MyApp.getInitialProps = async (appContext) => {
//   const appProps = await App.getInitialProps(appContext)
//   return { ...appProps }
// }

export default MyApp;

https://github.com/myeongjae-kim/next-js-with-typescript-valid-app-type

@EBEREGIT
Copy link

EBEREGIT commented Apr 16, 2020

Seems this is just for typescript only. I am using javascript

@myeongjae-kim
Copy link

myeongjae-kim commented Apr 17, 2020

@EBEREGIT

TypeScript is a super set of JavaScript, so just delete types then you can use it for JavaScript.

// import App from 'next/app'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext) => {
//   // calls page's `getInitialProps` and fills `appProps.pageProps`
//   const appProps = await App.getInitialProps(appContext);
//
//   return { ...appProps }
// }

export default MyApp

Also, be careful to use getInitialProps(I think you may know, but as a caution). getInitialProps of _app totally disable Static Site Generating and its optimization. Next.js recommends you to use getStaticProps or getServerSideProps instead of getInitialProps.

@timneutkens
Copy link
Member

The documentation covers _app as a function already: https://nextjs.org/docs/advanced-features/custom-app

Commented out getInitialProps from your examples @myeongjae-kim as in general adding it is a bad default if there's nothing happening in it.

@hrzon

This comment has been minimized.

@NMinhNguyen
Copy link
Contributor

Don't want to open a separate issue just yet but the OP was also talking about _document.js:

Currently it's not possible as _app.js and _document.js require to extend Next's React classes.

Do we know what's necessary to solve this for _document.js too, without using a wrapper component? Happy to open a dedicated issue if you think it's worthwhile.

@kimgiutae
Copy link

I'm also interested to have the custom Document as a functional component... Is this possible like the custom app?

@will-stone
Copy link

@NMinhNguyen I think it's probably worth opening a separate issue for this, as firstly, this issue is closed, and secondly, the title is about next/app and not next/document. Would be nice to see this, so we could use hooks.

@todortotev
Copy link
Contributor

me2

@timneutkens
Copy link
Member

Hid the last comment as it will likely cause your application to crash between upgrades in backwards incompatible ways because it does not follow the documented way of using _document.

Converting _document to a function component has no real benefits currently besides being able to use useContext but there's no context wrapper above _document that you can use with useContext. useState, useEffect, and other hooks will not work as well given that _document is not hydrated client-side.

_document is currently not a function component for backwards compat reasons, we can likely take a similar approach to what we did for _app though by having getInitialProps fall back to the default value.

Created #19355 to track this.

@kimgiutae
Copy link

Is the solution added in this recent PR? #28515

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.