Uncaught Invariant Violation: Too many re-renders.

2020-06-07 04:20发布

I'm trying to add a snackBar in order to display a message whenever a user signIn or not. SnackBar.jsx:

import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import CheckCircleIcon from "@material-ui/icons/CheckCircle";
import ErrorIcon from "@material-ui/icons/Error";
import CloseIcon from "@material-ui/icons/Close";
import green from "@material-ui/core/colors/green";
import IconButton from "@material-ui/core/IconButton";
import Snackbar from "@material-ui/core/Snackbar";
import SnackbarContent from "@material-ui/core/SnackbarContent";
import { withStyles } from "@material-ui/core/styles";

const variantIcon = {
  success: CheckCircleIcon,
  error: ErrorIcon
};

const styles1 = theme => ({
  success: {
    backgroundColor: green[600]
  },
  error: {
    backgroundColor: theme.palette.error.dark
  },
  icon: {
    fontSize: 20
  },
  iconVariant: {
    opacity: 0.9,
    marginRight: theme.spacing.unit
  },
  message: {
    display: "flex",
    alignItems: "center"
  }
});

function SnackbarContentWrapper(props) {
  const { classes, className, message, onClose, variant, ...other } = props;
  const Icon = variantIcon[variant];

  return (
    <SnackbarContent
      className={classNames(classes[variant], className)}
      aria-describedby="client-snackbar"
      message={(
        <span className={classes.message}>
          <Icon className={classNames(classes.icon, classes.iconVariant)} />
          {message}
        </span>
      )}
      action={[
        <IconButton
          key="close"
          aria-label="Close"
          color="inherit"
          className={classes.close}
          onClick={onClose}
        >
          <CloseIcon className={classes.icon} />
        </IconButton>
      ]}
      {...other}
    />
  );
}

SnackbarContentWrapper.propTypes = {
  classes: PropTypes.shape({
    success: PropTypes.string,
    error: PropTypes.string,
    icon: PropTypes.string,
    iconVariant: PropTypes.string,
    message: PropTypes.string,
  }).isRequired,
  className: PropTypes.string.isRequired,
  message: PropTypes.node.isRequired,
  onClose: PropTypes.func.isRequired,
  variant: PropTypes.oneOf(["success", "error"]).isRequired
};

const MySnackbarContentWrapper = withStyles(styles1)(SnackbarContentWrapper);

const CustomizedSnackbar = ({
  open,
  handleClose,
  variant,
  message
}) => {
  return (
    <div>
      <Snackbar
        anchorOrigin={{
          vertical: "bottom",
          horizontal: "left"
        }}
        open={open}
        autoHideDuration={6000}
        onClose={handleClose}
      >
        <MySnackbarContentWrapper
          onClose={handleClose}
          variant={variant}
          message={message}
        />
      </Snackbar>
    </div>
  );
};

CustomizedSnackbar.propTypes = {
  open: PropTypes.bool.isRequired,
  handleClose: PropTypes.func.isRequired,
  variant: PropTypes.string.isRequired,
  message: PropTypes.string.isRequired
};

export default CustomizedSnackbar;

SignInFormContainer.jsx:

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import SnackBar from '../../components/SnackBar';
import SignInForm from './SignInForm';

const SingInContainer = ({ message, variant}) => {
    const [open, setSnackBarState] = useState(false);
    const handleClose = (reason) => {
        if (reason === 'clickaway') {
          return;
        }
        setSnackBarState(false)

      };

    if (variant) {
        setSnackBarState(true);
    }
    return (
        <div>
        <SnackBar
            open={open}
            handleClose={handleClose}
            variant={variant}
            message={message}
            />
        <SignInForm/>
        </div>
    )
}

SingInContainer.propTypes = {
    variant: PropTypes.string.isRequired,
    message: PropTypes.string.isRequired
}

const mapStateToProps = (state) => {
    const {variant, message } = state.snackBar;

    return {
        variant,
        message
    }
}

export default connect(mapStateToProps)(SingInContainer);

When I run the application I got this error:

Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop.
at invariant (http://localhost:9000/bundle.js:34484:15)
at dispatchAction (http://localhost:9000/bundle.js:47879:44)
at SingInContainer (http://localhost:9000/bundle.js:79135:5)
at renderWithHooks (http://localhost:9000/bundle.js:47343:18)
at updateFunctionComponent (http://localhost:9000/bundle.js:49010:20)
at beginWork (http://localhost:9000/bundle.js:50020:16)
at performUnitOfWork (http://localhost:9000/bundle.js:53695:12)
at workLoop (http://localhost:9000/bundle.js:53735:24)
at HTMLUnknownElement.callCallback (http://localhost:9000/bundle.js:34578:14)
at Object.invokeGuardedCallbackDev (http://localhost:9000/bundle.js:34628:16)

The problem is due to the SnackBar component. I use the useStatehooks in order to change the state of the snackBar. Should I use a class and a componentShouldUpdate in order to not render multiple times?

4条回答
来,给爷笑一个
2楼-- · 2020-06-07 04:41

In SnackbarContentWrapper you need to change

<IconButton
          key="close"
          aria-label="Close"
          color="inherit"
          className={classes.close}
          onClick={onClose}
        >

to

<IconButton
          key="close"
          aria-label="Close"
          color="inherit"
          className={classes.close}
          onClick={() => onClose}
        >

so that it only fires the action when you click.

Instead, you could just curry the handleClose in SingInContainer to

const handleClose = () => (reason) => {
        if (reason === 'clickaway') {
          return;
        }
        setSnackBarState(false)

      };

It's the same.

查看更多
太酷不给撩
3楼-- · 2020-06-07 04:50

You must link an event in your onClick. Additionally, the click function must receive the event. See the example

export default function Component(props) {

    function clickEvent (event, variable){
        console.log(variable);
    }

    return (
        <div>
            <IconButton
                key="close"
                aria-label="Close"
                color="inherit"
                onClick={e => clickEvent(e, 10)}
            >
        </div>
    )
}
查看更多
beautiful°
4楼-- · 2020-06-07 04:51

I also have the same problem, and the solution is I didn't bind the event in my onClick. so when it renders for the first time and the data is more, which ends up calling the state setter again, which triggers React to call your function again and so on.

export default function Component(props) {

function clickEvent (event, variable){
    console.log(variable);
}

return (
    <div>
        <IconButton
            key="close"
            aria-label="Close"
            color="inherit"
            onClick={e => clickEvent(e, 10)} // or you can call like this:onClick={() => clickEvent(10)} 
        >
    </div>
)

}

查看更多
甜甜的少女心
5楼-- · 2020-06-07 05:08

I suspect that the problem lies in the fact that you are calling your state setter immediately inside the function component body, which forces React to re-invoke your function again, with the same props, which ends up calling the state setter again, which triggers React to call your function again.... and so on.

const SingInContainer = ({ message, variant}) => {
    const [open, setSnackBarState] = useState(false);
    const handleClose = (reason) => {
        if (reason === 'clickaway') {
          return;
        }
        setSnackBarState(false)

      };

    if (variant) {
        setSnackBarState(true); // HERE BE DRAGONS
    }
    return (
        <div>
        <SnackBar
            open={open}
            handleClose={handleClose}
            variant={variant}
            message={message}
            />
        <SignInForm/>
        </div>
    )
}

Instead, I recommend you just conditionally set the default value for the state property using a ternary, so you end up with:

const SingInContainer = ({ message, variant}) => {
    const [open, setSnackBarState] = useState(variant ? true : false); 
                                  // or useState(!!variant); 
                                  // or useState(Boolean(variant));
    const handleClose = (reason) => {
        if (reason === 'clickaway') {
          return;
        }
        setSnackBarState(false)

      };

    return (
        <div>
        <SnackBar
            open={open}
            handleClose={handleClose}
            variant={variant}
            message={message}
            />
        <SignInForm/>
        </div>
    )
}

Comprehensive Demo

See this CodeSandbox.io demo for a comprehensive demo of it working, plus the broken component you had, and you can toggle between the two.

查看更多
登录 后发表回答