Search code examples
reactjstypescriptmaterial-uinext.jsserver-side-rendering

in NEXTJS Warning: Expected server HTML to contain a matching <div> in <div>


I am using nextjs and mui. I am facing a warning when rendering pages. Here is my code. Please help to solve the issue!!!

import "../styles/globals.scss";
import { AppProps } from "next/app";
import useGetAuthentication from "../hooks/useGetAuthentication";
import States from "../interfaces/states";
import STATUS from "../constants/status";
import { CssBaseline } from "@mui/material";
import { ThemeProvider } from "@mui/material/styles";
import theme from "../styles/theme";
import Layout from "../layouts/Layout";
import Head from "next/head";
import * as React from "react";
import Login from "../components/Login";
import { Box } from "@mui/material";

interface MyAppProps extends AppProps {
  emotionCache?: EmotionCache;
}

const checkStatusCode = (statusCode: number): boolean => {
  return statusCode === STATUS.NOT_FOUND || statusCode === STATUS.INTERNAL_SERVER_ERROR;
};

function App({ Component, pageProps }: AppProps) {
  const { states } = pageProps;
  const { statusCode } = pageProps;
  const { isAuthorized } = useGetAuthentication(states as States);

  console.log("App -> Component", Component);
  console.log("App -> pageProps", pageProps);
  console.log("App -> states", states);
  console.log("App -> statusCode", statusCode);
  const drawerWidth: number = 240;
  if (!checkStatusCode(statusCode) && !isAuthorized)
    return (
      <Box
        component="main"
        sx={{
          flexGrow: 1,
          p: 3,
          width: { lg: "230px", sm: `calc(100% - ${drawerWidth}px)` }
        }}
      >
        <Login />
      </Box>
    );

  return (
    <>
      <Head>
        <meta name="viewport" content="initial-scale=1, width=device-width" />
      </Head>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </ThemeProvider>
    </>
  );
}

export default App;

Login component is below

    import React, { useEffect } from "react";
import authenticationStore from "../../stores/persistences/authenticationStore";
import TestHttp from "../../httpModules/testHttp";
import STATUS from "../../constants/status";
import RequestSignIn from "../../interfaces/test/requestSignIn";
import styles from "./login.module.scss";
import { Alert, FormControlLabel, Grid, Paper, TextField, Typography, Stack, Button, Checkbox } from "@mui/material";
import Image from "next/image";
import useLoginInputs from "../../hooks/useLoginInputs";
import LocalStorageHandler from "../../utils/localStorageHandler";
import RememberId from "../../interfaces/rememberId";
import ERROR_MESSAGE from "../../constants/errorMessage";
import LOGIN_INFO from "../../constants/loginInfo";

const Login: React.FC = () => {
  const localStorageHandler = new LocalStorageHandler<RememberId>();
  const authorize = authenticationStore((state) => state.authorize);
  const testHttp = new TestHttp();
  const { inputs, setInputs, isRememberChecked, isError, setIsError, isIdEmpty, setIsIdEmpty, isPasswordEmpty, setIsPasswordEmpty, inputsHandler, checkboxHandler } = useLoginInputs([
    "id",
    "password"
  ]);

  const { id, password } = inputs;

  const onLoginHandler = async (): Promise<void> => {
    if (isIdEmpty) {
      return setIsError(ERROR_MESSAGE.ID_EMPTY);
    }
    if (isPasswordEmpty) {
      return setIsError(ERROR_MESSAGE.PASSWORD_EMPTY);
    }
    const signInInfo: RequestSignIn = { id, password };
    const { statusCode, jsonResult } = await testHttp.signIn(false, signInInfo);

    /*
     *   인증 실패 (아이디, 비밀번호 일치 하지 않는 경우 등) 발생 시 코드 작성
     */

    if (statusCode !== STATUS.OK) {
      setIsError(true);
      return;
    }
    const { userInfo, tokenInfo } = jsonResult;
    authorize(statusCode, userInfo, tokenInfo);
    if (!isRememberChecked) return localStorageHandler.removeLocalStorageData(LOGIN_INFO.REMEMBER_ID);
    localStorageHandler.setLocalStorageData("rememberId", {
      id
    });
  };

  useEffect(() => {
    console.log("하이");
    setIsIdEmpty(id.length <= 0);
    setIsPasswordEmpty(password.length <= 0);
    setIsError(null);
  }, [id, password]);

  console.log("id", id);
  console.log("password", password);
  return (
    <Grid>
      <Paper elevation={10} className={styles.container}>
        <Grid align={"center"}>
          <div className={styles.logo}>
            <Image src={"/images/logo.svg"} width={"200px"} height={"80px"} alt={"logo"} />
            <Typography variant={"h6"}>관리자</Typography>
          </div>
        </Grid>
        <Stack spacing={1} justifyContent={"center"} alignItems={"center"} className={styles["login-container"]}>
          <TextField name={"id"} placeholder={"아이디를 입력해주세요."} required value={id} type={"text"} className={styles["login-input"]} onChange={inputsHandler} />
          <TextField name={"password"} placeholder={"비밀번호를 입력하세요."} required value={password} type={"password"} className={styles["login-input"]} onChange={inputsHandler} />
        </Stack>
        <Stack>
          <FormControlLabel control={<Checkbox checked={isRememberChecked} />} label={"아이디 저장"} className={styles.checkbox} onChange={checkboxHandler} />
        </Stack>
        <Stack justifyContent={"center"} alignItems={"center"}>
          <Button type={"submit"} color={"primary"} variant={"contained"} className={styles["login-button"]} size={"large"} onClick={onLoginHandler}>
            로그인
          </Button>
        </Stack>
        <Stack justifyContent={"center"} alignItems={"center"} className={styles["error-message"]}>
          <div>
            {isError && (
              <Alert severity={"error"}>
                <strong>{isError}</strong>
              </Alert>
            )}
          </div>
        </Stack>
      </Paper>
    </Grid>
  );
};

export default Login;

the warning is

Warning: Expected server HTML to contain a matching <div> in <div>.
    at div
    at eval (webpack-internal:///./node_modules/@emotion/react/dist/emotion-element-cbed451f.browser.esm.js:57:66)
    at Box (webpack-internal:///./node_modules/@mui/system/esm/createBox.js:36:72)
    at Layout (webpack-internal:///./layouts/Layout/index.tsx:16:26)
    at InnerThemeProvider (webpack-internal:///./node_modules/@mui/system/esm/ThemeProvider/ThemeProvider.js:21:70)
    at ThemeProvider (webpack-internal:///./node_modules/@mui/private-theming/ThemeProvider/ThemeProvider.js:47:5)
    at ThemeProvider (webpack-internal:///./node_modules/@mui/system/esm/ThemeProvider/ThemeProvider.js:41:5)
    at App (webpack-internal:///./pages/_app.tsx:61:27)
    at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/client.js:8:20638)
    at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/client.js:8:23179)
    at Container (webpack-internal:///./node_modules/next/dist/client/index.js:323:9)
    at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:820:26)
    at Root (webpack-internal:///./node_modules/next/dist/client/index.js:944:27)
window.console.error @ next-dev.js?3515:25

Solution

  • In progress value update from use hook can also causing this problem. To prevent it, just copy the variable value from hook to state from useEffect hook. Here is the example

    import { useAccount } from "wagmi";
    
    ...
    
      const { address, isConnected, isConnecting } = useAccount();
      // the value of these variable above are changed dynamically
      
      const [connectionStat, setConnectionStat] = useState();
      const [addr, setAddr] = useState();
    
      // copy the value to state here
      useEffect(() => {
        setConnectionStat(isConnected);
        setAddr(address);
      }, [address, isConnected])
    
      // then now we can display the value properly
    
      return (
        <div>
         <p>Connection status :  {connectionStat}</p>
         <p>Connected to      :  {addr}</p>
          ....
        </div>
      );
    

    Explanation: passing value directly from hook to JSX/view may causing inconsistent value inside JSX/view so the the page render can not be consistent as well and there will be different value between value in SSR and client.

    Hope it helps.