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.
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.
I am considering rewriting the above-mentioned components in my project folder, but I fear that it may cause some issues.
This is pretty much it.
This would be great! Would love to be able to use hooks in _app.tsx
!
Yep, functional component is the trend
@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.
@timneutkens do you mean It'll break withRouter
wherever it's being used, or just with _app.js
?
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.
@timneutkens Can you expand on the issue? Or point to somewhere it's discussed? Also, what's the plan to move forward?
@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.
Very cool. Thanks!
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.
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
Document should be a class extending from next/document as documented. This issue is completely unrelated to _document.
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.
@dmitrizzle So I don't write a class, I did this:
import Document, { Html, Head, Main, NextScript } from 'next/document';
function MyDocument() {
return (
<Html>
<Head>
<meta name="author" content="Bruno Edoh" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
MyDocument.getInitialProps = Document.getInitialProps;
MyDocument.renderDocument = Document.renderDocument;
export default MyDocument;
@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.
Which seems to work fine, but I'm not sure if this is the best approach, particularly regarding
pageProps
since I'm using aany
type.
you can easily type it like this:
import { NextPage } from 'next'
import { AppProps } from 'next/app'
const App: NextPage<AppProps> = ({ Component, pageProps }) => {
}
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;
}
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
Seems this is just for typescript only. I am using javascript
@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.
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.
@timneutkens
Could you replace codes of my comment as below and delete this comment?
// 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;
The getInitialProps I wrote is what I've seen quite long time ago. Using App.getInitialProps
like documentation seems better.
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.
I'm also interested to have the custom Document as a functional component... Is this possible like the custom app?
@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.
I'm also interested to have the custom Document as a functional component... Is this possible like the custom app?
+1
Are there any open issues on this?
me2
Most helpful comment
Here is the perfect _app type.
https://github.com/myeongjae-kim/next-js-with-typescript-valid-app-type