Next.js and Material-UI leading to invalid hook call? - reactjs

I'm trying to use material-ui with next.js / react.js.
When applying a layout file through _app.js I'm getting an error.
This is using the example react.js template material-ui provides (so confusing why it wouldn't work simply - i get the impression that material-ui has issues with next.js generally?).
I've tried a few different approaches, changing document and app, still won't seem to work.
Here's the Layout.js file
import React from 'react';
import clsx from 'clsx';
import { makeStyles } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import Drawer from '#material-ui/core/Drawer';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import IconButton from '#material-ui/core/IconButton';
import Badge from '#material-ui/core/Badge';
import Container from '#material-ui/core/Container';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import Link from '#material-ui/core/Link';
import MenuIcon from '#material-ui/icons/Menu';
import ChevronLeftIcon from '#material-ui/icons/ChevronLeft';
import NotificationsIcon from '#material-ui/icons/Notifications';
import { mainListItems, secondaryListItems } from '../Sidebar/Sidebar';
import Chart from '../Charts/Chart';
import Deposits from '../Deposits';
import Orders from '../Orders';
import Menu from '../AppBar/Menu';
const drawerWidth = 240;
const useStyles = makeStyles(theme => ({
........
export default function Layout() {
const classes = useStyles();
const [open, setOpen] = React.useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight);
return (
<div className={classes.root}>
<CssBaseline />
<Menu />
<Drawer
variant="permanent"
classes={{
paper: clsx(classes.drawerPaper, !open && classes.drawerPaperClose),
}}
open={open}
>
<div className={classes.toolbarIcon}>
<IconButton onClick={handleDrawerClose}>
<ChevronLeftIcon />
</IconButton>
</div>
<Divider />
<List>{mainListItems}</List>
<Divider />
</Drawer>
{props.children}
</div>
);
}
//<List>{secondaryListItems}</List> later
Heres app.js
import React from 'react';
import App, { Container } from 'next/app';
import Head from 'next/head';
import { ThemeProvider } from '#material-ui/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import theme from '../src/theme';
import Layout from '../components/Layout/Layout';
class MyApp extends App {
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Head>
<title>My page</title>
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Layout>
<Component {...pageProps} />
</ Layout>
</ThemeProvider>
</Container>
);
}
}
export default MyApp;
and document.js
import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/styles';
import theme from '../src/theme';
class MyDocument extends Document {
render() {
return (
<html lang="en">
<Head>
<meta charSet="utf-8" />
{/* Use minimum-scale=1 to enable GPU rasterization */}
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
/>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
MyDocument.getInitialProps = async ctx => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
<React.Fragment key="styles">
{initialProps.styles}
{sheets.getStyleElement()}
</React.Fragment>,
],
};
};
export default MyDocument;
And here is the error information.
×
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://xxxx-invalid-hook-call for tips about how to debug and fix this problem.
▶ 11 stack frames were collapsed.
ctx.renderPage
./pages/_document.js:60
57 | const sheets = new ServerStyleSheets();
58 | const originalRenderPage = ctx.renderPage;
59 |
> 60 | ctx.renderPage = () =>
| ^ 61 | originalRenderPage({
62 | enhanceApp: App => props => sheets.collect(<App {...props} />),
63 | });
View compiled
▶ 4 stack frames were collapsed.
This screen is visible only in development. It will not appear if the app crashes in production.
Open your browser’s developer console to further inspect this error.

As far as I am aware you cannot use hooks inside class components and your MyDocument is declared as a class. Maybe try converting it to a functional component.

I solve this problem installing npm i #emotion/react #emotion/styled

Related

How to both dynamically import and render a component on button click in nextjs?

I know that there are several nextjs and reactjs questions out there in this topic but I think this is specific enough to get an own thread. So I have a NextJS project where I want a button to disappear on click and then import a component dynamically and render it.
It is crucial to import the component dynamically because of 2 things.
because we work with a library in the component which relies on 'window'
because the component affects load time and we only need it when the 'init button' is clicked
Here is my index.js code so far:
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import dynamic from "next/dynamic"
import React, { useEffect, useState } from "react";
import { Inter } from "#next/font/google";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
return (
<>
<Head>
<title>My page</title>
<meta
name='description'
content="Some desc. content"
/>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' href='/favicon.ico' />
</Head>
<button onClick={initSurvey()}>Initialize!</button>
</>
)
}
function initSurvey(){
console.log("The button is working!")
const Survey = dynamic(() => import("../components/SurveyComponent"), {
ssr: false,
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Please keep in mind this is not all I could do I just cleaned up the failed attempts to deliver you a clean, readable and understandable snippet.
Thank you in advance!
By checking the React.lazy documentation and this guide, I managed to get to this solution:
import { lazy, Suspense, useState } from "react";
const dynamicComponent = lazy(() => import('../components/MyDynamicComponent');
export default function Home() {
const [dynComp, setDynComp] = useState(<div/>);
const loadDynamicComponent = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<dynamicComponent/>
</Suspense>
)
}
return (
<>
<button onClick={ () => setDynComp(<div>{loadDynamicComponent()}</div>)}>Initialize!</button>
{ dynState }
</>
);
}
Test it and let me know if it helps you!

chakra ui custom theme doesn't work at all in my next js project

I'm trying to build nextjs project with chakra UI, but my custom theme doesn't working at all, I've tried everything in chakra docs, even custom color which I've added doesn't work, but default colors of the chakra works correctly, I wanna info that I've setup dark/light mode using chakra docs, if it has something to to with my problem
My theme.js file
import { extendTheme } from "#chakra-ui/react";
import { mode } from "#chakra-ui/theme-tools";
const styles = {
global: (props) => ({
body: {
bg: mode("#a8dadc", "#006d77")(props),
color: mode("#006d77", "#a8dadc")(props),
},
}),
};
const colors = {
primary: "#e29578",
};
const theme = extendTheme({ styles, colors });
export default theme;
My index.tsx file
import {
Box,
Button,
useColorMode,
Text,
} from "#chakra-ui/react";
import type { NextPage } from "next";
import Head from "next/head";
const Home: NextPage = () => {
const { colorMode, toggleColorMode } = useColorMode();
return (
<div>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1>Home Page</h1>
<Box mb={4}>This boxs style will change based on the color mode.</Box>
<Button onClick={toggleColorMode} color="primary">
Toggle {colorMode === "light" ? "Dark" : "Light"}
</Button>
<Text fontSize="6xl" color="primary">
{" "}
Custom color not working
</Text>
</div>
);
};
export default Home;
My _app.tsx file
import type { AppProps } from "next/app";
import { ChakraProvider } from "#chakra-ui/react";
import { Chakra } from "../components/wrappers/Chakra";
import theme from "../libs/theme";
function MyApp({ Component, pageProps }: AppProps) {
return (
<ChakraProvider theme={theme}>
<Chakra cookies={pageProps.cookies}>
<Component {...pageProps} />
</Chakra>
</ChakraProvider>
);
}
export { getServerSideProps } from "../components/wrappers/Chakra";
export default MyApp;
I have the same issue!
I've noticed that there are some default css variables attached to :root an the ChakraProvider is not able to override them by default, so I've set the cssVarsRoot as a workarround.
<ChakraProvider theme={theme} cssVarsRoot="body">

Why material UI is broken after a hard refresh in Nextjs

I have a problem with a new nextjs application, I have configured Material UI
Each time I refresh the page (hard) the style is not correct anymore, I have to redo modifications in each file to find my previous style...
I show you my _app and _document files
Thanks in advance for your help
_app.js
import React from "react";
import { CacheProvider } from "#emotion/react";
import { CssBaseline } from "#mui/material";
import { ThemeProvider } from "#mui/styles";
import createEmotionCache from "../styles/createEmotionCache";
import theme from "../config/MUITheme";
const clientSideEmotionCache = createEmotionCache();
const MyApp = ({ Component, emotionCache = clientSideEmotionCache, pageProps }) => (
<CacheProvider value={emotionCache}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
export default MyApp;
_document.js
import * as React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import createEmotionServer from "#emotion/server/create-instance";
import createEmotionCache from "../styles/createEmotionCache";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
/* eslint-disable */
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
/* eslint-enable */
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
data-emotion={${style.key} ${style.ids.join(" ")}}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), ...emotionStyleTags],
};
};
Before refresh :
After refresh :
Environment :
"next": "12.1.0",
"react": "17.0.2",
"#emotion/cache": "^11.7.1",
"#emotion/react": "^11.8.1",
"#emotion/server": "^11.4.0",
"#emotion/styled": "^11.8.1",
"#fontsource/roboto": "^4.5.3",
"#mui/icons-material": "^5.4.4",
"#mui/material": "^5.4.4",
"#mui/styles": "^5.4.2"
You can reproduce with : https://codesandbox.io/s/throbbing-rain-72v10e
I had the same problem for months and I realized even though your app.js and document.js is fine by hard refreshing it takes a bit to read theme(I still don't know why). My workaround for this was fully load the components that had this issue on client side. Components that don't need to be SSR you can turned off SSR feature for them. you can do this in two ways
turn off SSR when importing:
export const component = dynamic(
() => import('../components/component'),
{
ssr: false,
}
);
turn off ssr with a wrapper around component:
import React from 'react';
import dynamic from 'next/dynamic';
const NoSSRWrapper = props => <React.Fragment>{props.children}</React.Fragment>;
export default dynamic(() => Promise.resolve(NoSSRWrapper), {
ssr: false,
});
return (
<NoSSRWrapper>
//your component code
</NoSSRWrapper>
)
this way everything Waites and loads fully on client side therefore refreshing/hard refreshing doesn't affect the load of CSS.
this link was really helpful for me

How to connect Material UI v5 to Next JS v12?

Recently, Material UI 5 was released. Previously (in Material UI 4), I used to connect it by modifying _document.js and _app.js. Is it the same for Material UI 5?
for MUI v4 _app.js
import CssBaseline from '#material-ui/core/CssBaseline';
import {ThemeProvider} from '#material-ui/core/styles';
class MyApp extends App {
render() {
const {Component, pageProps, router} = this.props;
return (
<ThemeProvider>
<CssBaseline />
<Component {...pageProps}/>
</ThemeProvider>
)
}
}
export default MyApp
for MUI v4 _document.js
import Document, {Html, Main, NextScript} from "next/document";
import { ServerStyleSheets } from '#material-ui/core/styles';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
let props = {...initialProps};
return props;
}
render() {
return (
<Html>
<body>
<Main/>
<NextScript/>
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and register rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
What I want to ask is: Should I connect it in the same way to MUI v5?
Maybe there is a more elegant way to do it?
It is connected still the same way as I provided in my question, by some changes in _app.js and _document.js
Official MUI example with Next js
https://github.com/mui/material-ui/tree/master/examples/nextjs
If it's just css you're importing from #materail-ui/core/styles, you can simply import the CSS directly in your _App.js with...
import '#materail-ui/core/styles/someFilename.css' (no from needed) And it should be accessible anywhere in your app from there.

Next.js 9+ FOUC (Flash or Unstyled Content) with Styled Components

Have spent a couple days on this issue sourcing solutions and ideas from most of SA and Reddit however to no avail..
Upon load both in production and local every load presents the whole html without any styling before then being injected into DOM..
Currently this is my projects key files:
_document.js
import { ServerStyleSheet } from "styled-components";
import Document, { Main, NextScript } from "next/document";
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
return (
<html lang="en">
<Head>
<title>namesjames</title>
<link rel="icon" href="/favicon.ico" />
<script src="/static/chrome-fix.js" />
<link href="/above-the-fold.css" />
</Head>
<body>
<script src="/noflash.js" />
<Main />
<NextScript />
<script> </script>
</body>
</html>
);
}
}
_app.js
/* eslint-disable class-methods-use-this */
import App from "next/app";
import React from "react";
import { ThemeProvider } from "styled-components";
import { ParallaxProvider } from 'react-scroll-parallax';
import Header from "../components/Header";
import theme from "../theme";
import GlobalStyles from "../GlobalStyles";
import DarkModeToggle from '../components/toggle/toggleMode';
import Footer from '../components/Footer'
import LazyLoad from 'react-lazy-load';
import Router from 'next/router';
import styled from 'styled-components'
// import '../style.css'
const Loaded = styled.div`
opacity: ${(props) => props.loaded ? "1" : "0"};
`
export default class MyApp extends App {
state = { isLoading: false, loaded: false }
componentDidMount() {
// Logging to prove _app.js only mounts once,
// but initializing router events here will also accomplishes
// goal of setting state on route change
console.log('MOUNT');
this.setState({loaded: true})
Router.events.on('routeChangeStart', () => {
this.setState({ isLoading: true });
console.log('loading is true, routechangeStart')
});
Router.events.on('routeChangeComplete', () => {
this.setState({ isLoading: false });
console.log('loading is false, routeChangeComplete')
});
Router.events.on('routeChangeError', () => {
this.setState({ isLoading: false });
console.log('loading is false, routeChangeError')
});
}
render(): JSX.Element {
const { isLoading } = this.state;
const { Component, pageProps, router, loaded } = this.props;
return (
<Loaded loaded={this.state.loaded}>
<ThemeProvider theme={theme}>
<GlobalStyles />
<ParallaxProvider>
<Header />
{isLoading && 'STRING OR LOADING COMPONENT HERE...'}
<Component {...pageProps} key={router.route} />
<LazyLoad offsetVertical={500}>
<Footer />
</LazyLoad>
</ParallaxProvider>
<DarkModeToggle />
</ThemeProvider>
</Loaded>
);
}
}
index.js
import { color } from "styled-system";
import { OffWhite } from "../util/tokens";
import Hero from "../components/Hero";
import Banner from '../components/Banner'
import TitleText from '../components/TitleText'
import HomeTitleCopyScene from '../components/HomeTitleCopyScene'
import TwoCards from '../components/TwoCards'
import LazyLoad from 'react-lazy-load';
function Home(): JSX.Element {
return (
<div className="container">
<Banner />
<HomeTitleCopyScene />
<LazyLoad offsetVertical={1000}>
<TwoCards />
</LazyLoad>
</div>
);
}
export default Home;
As some might see I have tried multiple implementations and am just a bit confused as to what it could be at this stage..
Any help appreciated and I can provide more information upon request.. Many thanks
I found two solutions which helped -->
Was to hard style opacity:0 into the JSX and then upon styling injecting into DOM applying opacity: 1 !important onto any of the components displayed..
<section className="cards-block" style={{opacity:0}}>
Whilst this was effective this morning I discovered at some point during my development I had incorrectly imported Head from next/head and used this in my _document.js rather than using the correct Head from next/documents..
// import Head from "next/head"; --> incorrect
import { ServerStyleSheet } from "styled-components";
import Document, { Head, Main, NextScript } from "next/document";
Ergo -> a correctly rendering and injected element with no FOUC
Hope this helps someone out there
I've found a workaround for my small portfolio project:
just included this inline css to a <head> of my custom _document.js:
{<style dangerouslySetInnerHTML={{__html: `
html {background: #333}
body #__next div {visibility: hidden}
body.loaded #__next div {visibility: visible}
`}}></style>}
The "loaded" class is added to the body in _app.js:
if (process.browser) {
document.body.classList.add("loaded")
}
I'm hot sure if it's a good solution, any advice would be appreciated :)

Resources