未捕获的不变式违规:重新渲染次数过多。React限制了渲染次数以防止无限循环。

177

我正在尝试添加一个 snackBar ,以便在用户登录或未登录时显示消息。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);

当我运行应用程序时,我遇到了这个错误:

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)

问题是由于SnackBar组件引起的。我使用useState钩子来改变SnackBar的状态。我是否应该使用类和componentShouldUpdate,以便不渲染多次?
问题原因是SnackBar组件导致的。我使用useState钩子来更改SnackBar的状态。为了避免多次渲染,我是否应该使用类和componentShouldUpdate呢?

1
你可能正在使用Webpack,如果是这样,我想提醒你关注devtool选项,它可以让你精确定位到原始代码中的错误,而不是像现在一样在捆绑代码中定位错误。 - Nino Filiu
13
请问您能否检查一下 handleClose 是否被多次调用了,如果将 handleClose={handleClose} 改为 handleClose={()=>handleClose} 是否可以解决这个问题? - Nicholas
@Nicholas 我试过了,但是我得到了同样的错误。 - Slim
我也遇到了类似的问题,但在我的情况下,是由于来自先前渲染的过时值,我已经在useCallback中传递了空依赖数组并且不必要地更新状态。 - Wasit Shafi
我曾经遇到过类似的错误,但那是由于 API 调用失败并且 API 自动重试导致的。 - Yug Singh
@NinoFiliu 如果他们能提供一些关于在哪里进行这些所谓的改变的基本示例,那就太好了。听起来好像你需要一个关于Webpack的博士学位才能理解他们在说什么。有没有可能你能总结一下? - undefined
18个回答

2

我也有同样的问题,解决方法是我没有在onClick中绑定事件。 所以当它首次渲染并且数据更多时,这最终会再次调用状态设置器,从而触发React再次调用您的函数,如此循环。

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

2

基本上,您只需确保将状态设置器作为事件侦听器的回调来调用,这样函数就不会在没有动作的情况下触发。

像这样:

onClick={() => stateSetterFunction()}

1

这个错误是因为您在return语句中的函数名称后面使用了括号。

import React, { useState } from "react";

计数器 = () => { const [数字, 设置数字] = useState(0)

const increment = () => {
    setnum((prev) => {
        return prev + 1
    })
    setnum((prev) => {
        return prev + 1
    })

}

const decrement = () => {
    setnum(num - 1)
}

return (
    <div>
        <button onClick={increment}>+</button>
        {num}
        <button onClick={decrement}>-</button>
    </div>
)

请勿在返回部分中使用 ::: onClick= {increment()} 或 onClick= {decrement()}

导出 Count;


1
只有一个小建议,总是在某个动作上调用函数,像这样:onClick{()=>func()}而不是onClick{func()}因为后者可能会由于多次调用而导致重新渲染的问题。

0

我认为你可以做到这一点。在我的地方,它可以毫无问题地运行。

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

<SnackBar
    open={open}
    handleClose={()=>handleClose(r)}
    variant={variant}
    message={message}
    />

0
在我的情况下,是因为错误的属性名称onblur={setFieldTouched('firstName')} -->onBlur={()=>setFieldTouched('firstName')}导致问题。修改属性名称后,错误消失了。

0

请确保您是否包含了preventDefault()。在我的情况下,我忘记使用它,后来发现了这个错误。


0

你可能在传递setState时立即调用它,请确保你不是这样设置的:

variable={setState()}

相反,应该像这样设置:

variable={setState}

有时,你会将setState放在一个函数中,并直接调用该函数,从而导致此问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接