Next.js: Using _app.js, _document.js with react hooks - Functional Implementation

Created on 3 Apr 2019  路  3Comments  路  Source: vercel/next.js

Feature request

Is your feature request related to a problem? Please describe.

The current implementation of _app.js, _document.js requires you to extend the App and the Document class exported by Next. Since hooks cannot be added in class based components, it is currently not possible to use hooks here

Describe the solution you'd like

Export a functional implementation of App, Document, Error. Or maybe we can provide hooks that address the functionality that these offer.

Describe alternatives you've considered

Currently, we could add a top-level component to _app.js and pass the page as a child to this component. This component would be functional, and can use hooks to define state, and add effects.

All 3 comments

Can this be looked at again?

I'm trying to create a custom _app.js file in an npm package so that I can simply use the following as project/pages/_app.js:

import AppProvider from "myPackage/customApp.js";

export default AppProvider;

However this fails as the Next.js only works if the npm package is in ES5, which doesn't support classes.

For example, compiling this:

import React from "react";
import App, { Container } from "next/app";
import CssBaseline from "@material-ui/core/CssBaseline";
import { ThemeProvider } from "@material-ui/styles";
import theme from "../theme/createTheme";
import { StateProvider } from "../state/store";
import Snackbar from "../component/Snackbar";

// ::::::::::::::::::::::::::::::::::::::::::::::::
// Component
// ::::::::::::::::::::::::::::::::::::::::::::::::

class AppProvider extends App {
  componentDidMount(): void {
    // Remove the server-side injected CSS
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles && jssStyles.parentNode) {
      jssStyles.parentNode.removeChild(jssStyles);
    }
  }

  render(): JSX.Element {
    const { Component, pageProps } = this.props;
    return (
      <Container>
        <StateProvider>
          <ThemeProvider theme={theme}>
            <CssBaseline />
            <Component {...pageProps} />
            <Snackbar />
          </ThemeProvider>
        </StateProvider>
      </Container>
    );
  }
}

export default AppProvider;

Turns it into:

"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importDefault(require("react"));
var app_1 = __importStar(require("next/app"));
var CssBaseline_1 = __importDefault(require("@material-ui/core/CssBaseline"));
var styles_1 = require("@material-ui/styles");
var createTheme_1 = __importDefault(require("../theme/createTheme"));
var store_1 = require("../state/store");
var Snackbar_1 = __importDefault(require("../component/Snackbar"));
var AppProvider = (function (_super) {
    __extends(AppProvider, _super);
    function AppProvider() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    AppProvider.prototype.componentDidMount = function () {
        var jssStyles = document.querySelector("#jss-server-side");
        if (jssStyles && jssStyles.parentNode) {
            jssStyles.parentNode.removeChild(jssStyles);
        }
    };
    AppProvider.prototype.render = function () {
        var _a = this.props, Component = _a.Component, pageProps = _a.pageProps;
        return (react_1.default.createElement(app_1.Container, null,
            react_1.default.createElement(store_1.StateProvider, null,
                react_1.default.createElement(styles_1.ThemeProvider, { theme: createTheme_1.default },
                    react_1.default.createElement(CssBaseline_1.default, null),
                    react_1.default.createElement(Component, __assign({}, pageProps)),
                    react_1.default.createElement(Snackbar_1.default, null)))));
    };
    return AppProvider;
}(app_1.default));
exports.default = AppProvider;

Which then causes the following error when I try to run npm run dev or npm run build:

TypeError: Class constructor App cannot be invoked without 'new'
    at new AppProvider (C:\...\node_modules\myPackage\customApp.js:47:42)
    at processChild (C:\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2846:14)
    at resolve (C:\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:2812:5)
    at ReactDOMServerRenderer.render (C:\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3202:22)
    at ReactDOMServerRenderer.read (C:\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3161:29)
    at renderToString (C:\...\node_modules\react-dom\cjs\react-dom-server.node.development.js:3646:27)
    at render (C:\...\node_modules\next-server\dist\server\render.js:85:16)
    at renderPage (C:\...\node_modules\next-server\dist\server\render.js:234:20)
    at ctx.renderPage (C:\...\node_modules\myPackage\customDocument.js:100:28)
    at C:\...\node_modules\next\dist\pages\_document.js:4:232
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (C:\...\node_modules\@babel\runtime-corejs2\helpers\asyncToGenerator.js:5:24)
    at _next (C:\...\node_modules\@babel\runtime-corejs2\helpers\asyncToGenerator.js:27:9)
    at C:\...\node_modules\@babel\runtime-corejs2\helpers\asyncToGenerator.js:34:7
    at new Promise (<anonymous>)
    at new F (C:\...\node_modules\core-js\library\modules\_export.js:36:28)

@TidyIQ same issue, after upgrade to next.9

Was this page helpful?
0 / 5 - 0 ratings

Related issues

knipferrc picture knipferrc  路  3Comments

rauchg picture rauchg  路  3Comments

havefive picture havefive  路  3Comments

kenji4569 picture kenji4569  路  3Comments

irrigator picture irrigator  路  3Comments