6

enter image description here

Video reproducing the error/missing css

I know there are already dated versions of this question on stack overflow, like React + Material-UI - Warning: Prop className did not match.

However, when I attempt to google and research people's solutions, there is just no clear answer. Any answers I could find don't match my stack.

My stack:

  • Node JS
  • Next JS
  • Material UI

And from what I could glean from answers to questions like next.js & material-ui - getting them to work is that there is some measure of incompatibility when it comes to Next JS and Material UI.

Code-wise, here is my Appbar component. Initially I was not exporting my useStyles object, but I ended up doing it in a pitiful attempt to follow along with Material UI's express guide to "server rendering". There has to be a fix that doesn't involve changing like every file I have.

import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
import InputBase from '@material-ui/core/InputBase';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { makeStyles } from '@material-ui/core/styles';
import MenuIcon from '@material-ui/icons/Menu';
import SearchIcon from '@material-ui/icons/Search';
import {connectSearchBox} from 'react-instantsearch-dom';

const useStyles = makeStyles(theme => ({
    root: {
        flexGrow: 1,
    },
    menuButton: {
        marginRight: theme.spacing(2),
    },
    title: {
        flexGrow: 1,
        display: 'none',
        [theme.breakpoints.up('sm')]: {
            display: 'block',
        },
    },
    search: {
        position: 'relative',
        borderRadius: theme.shape.borderRadius,
        backgroundColor: fade(theme.palette.common.white, 0.15),
        '&:hover': {
            backgroundColor: fade(theme.palette.common.white, 0.25),
        },
        marginLeft: 0,
        width: '100%',
        [theme.breakpoints.up('sm')]: {
            marginLeft: theme.spacing(1),
            width: 'auto',
        },
    },
    searchIcon: {
        width: theme.spacing(7),
        height: '100%',
        position: 'absolute',
        pointerEvents: 'none',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
    },
    inputRoot: {
        color: 'inherit',
    },
    inputInput: {
        padding: theme.spacing(1, 1, 1, 7),
        transition: theme.transitions.create('width'),
        width: '100%',
        [theme.breakpoints.up('sm')]: {
            width: 300,
            '&:focus': {
                width: 400,
            },
        },
    }
}));

function SearchBox({currentRefinement, refine}){
    const classes = useStyles();
    return(
        <InputBase
            type="search"
            value={currentRefinement}
            onChange={event => refine(event.currentTarget.value)}
            placeholder="Search by state, park name, keywords..."
            classes = {{
                root: classes.inputRoot,
                input: classes.inputInput,
            }}
        />
    )
}

const CustomSearchBox = connectSearchBox(SearchBox);

function SearchAppBar() {
    const classes = useStyles();
    return (
        <div className={classes.root}>
            <AppBar position="static" color="primary">
                <Toolbar>
                    <IconButton
                        edge="start"
                        className={classes.menuButton}
                        color="inherit"
                        aria-label="Open drawer"
                    >
                        <MenuIcon />
                    </IconButton>
                    <Typography className={classes.title} variant="h6" noWrap>
                        Title
                    </Typography>
                    <div className={classes.search}>
                        <div className={classes.searchIcon}>
                            <SearchIcon />
                        </div>
                        <CustomSearchBox/>
                    </div>
                </Toolbar>
            </AppBar>
        </div>
    );
}

export {SearchAppBar, useStyles};
notacorn
  • 3,526
  • 4
  • 30
  • 60
  • I had the same problem in Next.js. See https://stackoverflow.com/a/58626730/2728710 – pom421 Oct 30 '19 at 13:43
  • 2
    In my case, the source of the problem was that I was having different state on server vs on client. If you're checking for window object or localStorage, and conditionally adding something to the state, that might be it. – Daniel Turuș Sep 27 '20 at 18:12
  • If still struggling, try this: https://stackoverflow.com/a/69787927/10146901 – Firoj Siddiki Oct 31 '21 at 15:52

2 Answers2

9

I was just digging around random parts of the internet looking for answers to this error, accidentally npm install'ed styled-components as part of this answer on a Github issue (because they have a very similar object to the counterpart in Material UI called ServerStyleSheet (vs Material UI's ServerStyleSheets), so obviously that didn't work.

BUT......... I ended up just using the ServerStyleSheet fix to try to make it agreeable with Material UI's ServerStyleSheets object, and ended up with this new _document.js.

I'm still dumbfounded I was able to refactor an entirely different fix to make this work but I tested it and it fixes the problem entirely, now reloads are fine.

import Document, { Html, Head, Main, NextScript } from 'next/document';
import {ServerStyleSheets} from "@material-ui/styles";

class MyDocument extends Document {
    static async getInitialProps(ctx) {
        const sheet = new ServerStyleSheets();
        const originalRenderPage = ctx.renderPage;

        try{
            ctx.renderPage = () => originalRenderPage({
                enhanceApp: App => props => sheet.collect(<App {...props}/>)
            });

            const initialProps = await Document.getInitialProps(ctx);
            return { ...initialProps,
                styles: (
                    <>
                        {initialProps.styles}
                        {sheet.getStyleElement()}
                    </>
                )
            }
        } finally {
            ctx.renderPage(sheet)
        }

    }
    render() {
        return (
            <Html>
                <Head>
                    <link rel="shortcut icon" type="image/png" href="../static/favicon.ico"/>
                    <style>{`body { margin: 0 } /* custom! */`}</style>
                    <meta name="viewport"content="width=device-width, initial-scale=1.0" />
                </Head>
                <body className="custom_class">
                    <Main />
                    <NextScript />
                </body>
            </Html>
    )}
}

export default MyDocument;

If you wanna see how crazy it was that it worked, here is the fix for the same error in styled-components:

export default MyDocument;

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

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()
        }
    }
}

I hope this helped someone with the mess that is Material-UI + Next.js

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
notacorn
  • 3,526
  • 4
  • 30
  • 60
  • This one works for me on meterial-ui. But it only works on dev environment. when i run it with production build i got following error, TypeError: Cannot read property 'map' of undefined Works on dev production build - pass production run - fails – sameera madushan Jun 22 '19 at 17:06
  • 1
    This answer absolutely saved my butt. AMAZING! Thanks a million! – Joel Hager Mar 27 '21 at 20:36
  • Thanks a lot man! You're really saved my ____ :* – Angrej Kumar Jul 20 '21 at 17:00
5

For my part, adding { name: "MuiExample_Component" } in the makeStyle hook works for some reason. I found this solution while digging on internet. I would appreciate if someone could tell me if it's a good solution or not, but here is the code :

const useStyles = makeStyles({
    card: {
        backgroundColor: "#f7f7f7",
        width: "33%",
    },
    title: {
        color: "#0ab5db",
        fontWeight: "bold",
    },
    description: {
        fontSize: "1em"
    }
}, { name: "MuiExample_Component" });
Cizia
  • 428
  • 5
  • 11
  • 2
    Providing an answer constitutes you knowing whether it works or not. Otherwise just provide the link from where you got this code in the comments – shuberman Aug 02 '21 at 13:48
  • I don't know why this works but it does. Would love to get a link of the source though. – Jude Fernandes Oct 23 '21 at 08:33
  • This works because you explicitly set the name of the css class, so both server and client versions will use the same className. Had you ommited this option, each time mui renders this component a random name will be produced, thus server-rendered className will vary from the client-rendered one. – antonis Dec 24 '21 at 08:49