Will
Will

Reputation: 45

There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering

I have started to receive some errors and warnings:

Based on a search of similar issues, I think somehow there might be a <button> inside a <button>, but I cannot see where or how that could've happened!

The errors started when I moved some JSX code from a component into a static method of a class, which I would like to use as a UI factory for multiple components that all would use the same JSX code.

The following code was perfectly fine before the change:

AppsButton.jsx

import * as React from 'react';
import { styled, useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import AppsIcon from '@mui/icons-material/Apps';

// define a function outputting the view
function AppsButtonView (inputs) {
    // use the MUI theme
    const theme = useTheme();
    
    // define the view
    const view = (
        <Box
            sx = {{
                boxSizing: 'border-box',
                width: `calc(${theme.spacing(6)} + 1px)`
            }}
        >
            <IconButton
                aria-label = "open apps drawer"
                edge = "start"
                onClick = {inputs.callback}
                sx = {{
                    color: inputs.toggleState? "grey" : "inherit",
                    backgroundColor: inputs.toggleState? "white" : "inherit",
                }}
            >
                <AppsIcon />
            </IconButton>
        </Box>
    );

} // function

// export the function
export default AppsButtonView

... which is called by the containing AppBar component:

AppBar.jsx

import * as React from 'react';
import Box from '@mui/material/Box';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
//import * as AppBarContent from './/AppBar'; // todo work out how to import as module/package
import AppBarModel from "./AppBar.vm" // todo: include in above?
import AppsButton from "./AppsButton";
import HomeButton from "./HomeButton";
import Typography from '@mui/material/Typography';
import HelpButton from "./HelpButton";
import HelpDrawer from "./HelpDrawer";
import AppsDrawer from "./AppsDrawer";

// define a function outputting the view
function AppBarView (inputs) {

    // get the app bar model
    const appBarModel = AppBarModel();

    // define the view
    const view = (
        <Box sx={{ display: 'flex' }}>
            <AppBar
                position="fixed"
                sx={{ 
                    zIndex: (theme) => theme.zIndex.drawer + 1 
                }}
            >
                <Toolbar>
                    <AppsButton 
                        callback = {appBarModel.appsButtonCallback}
                        toggleState = {appBarModel.appsDrawerModel.state}
                    />
                    <HomeButton visibility = {inputs.showHomeButton} callback = {appBarModel.homeButtonCallback}/>
                    <Typography 
                        variant = "h6"
                        noWrap component = "div"
                        sx = {{
                            flex: 1,
                        }}
                    >
                        {inputs.title}
                    </Typography>
                    <HelpButton
                        callback = {appBarModel.helpButtonCallback}
                        toggleState = {appBarModel.helpDrawerModel.state}
                    />
                </Toolbar>
            </AppBar>
            <HelpDrawer model = {appBarModel.helpDrawerModel}/>
            <AppsDrawer model = {appBarModel.appsDrawerModel}/>
        </Box>
    );

    // output the view
    return view
    
} // function

// export the function
export default AppBarView
(there are multiple such buttons I would like to share some common JSX code for in this AppBar).

So the change I made was to introduce a UI Factory:

Factory.js

import { styled, useTheme } from '@mui/material/styles';
import Box from '@mui/material/IconButton';
import IconButton from '@mui/material/IconButton';

/*
Facility to output consistent UI components.
*/
class UIFactory {

    /*
    Returns a material UI icon button in a box.
    */
    static getBoxIconButton(inputs) {
    
        // use the MUI theme
        const theme = useTheme();

        // define the icon button UI
        const iconButton = (
            <Box
                sx = {{
                    boxSizing: 'border-box',
                    width: `calc(${theme.spacing(6)} + 1px)`
                }}
            >
                <IconButton
                    color = "inherit"
                    aria-label = {inputs.ariaLabel}
                    edge = "start"
                    onClick = {inputs.callback}
                >
                    {inputs.icon}
                </IconButton>
            </Box>
        );

        // output the icon button
        return iconButton;

    } // getBoxIconButton

} // class

// export the class
export default UIFactory

... which is now called in updated the AppsButton component like so:

AppsButton.jsx

import * as React from 'react';
import AppsIcon from '@mui/icons-material/Apps';
import UIFactory from "../Factory";

// define a function outputting the view
function AppsButtonView (inputs) {

    // make a button view
    const buttonDetails = {
        ariaLabel: "open apps drawer",
        icon: (<AppsIcon />),
        callback: inputs.callback,
    };
    const view = UIFactory.getBoxIconButton(buttonDetails)
    
    // output the view
    return view
    
} // function

// export the function
export default AppsButtonView

This same concept I successfully applied already with a drawer getting a of , etc. from the UI Factory.

So in my head I just moved the code to another file to be called from there instead, what am I missing here?

How is there a <button> inside a <button> with this new structure?

Upvotes: 2

Views: 1777

Answers (1)

Will
Will

Reputation: 45

So the answer to this question is in the question itself. There's actually nothing wrong with the code posted here, which works fine without the hydration error as I found out when copying back from this question in order to reproduce and demonstrate to someone. The code in the question came from the code used in my case and in copying to the question, I lost the real issue (which I still don't know - probably something small and silly).

Maybe an answer would be: "use sandboxing".

Upvotes: 1

Related Questions