未捕获的不变违反: 太多的重新渲染。反应限制渲染的数量,以防止无限循环

我试图添加一个 snackBar,以便在用户登录或不登录时显示消息。 返回文章页面点心吧:

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 组件上。我使用 useStatehooks 来更改 snackBar 的状态。我应该使用一个类和一个 componentShouldUpdate为了不呈现多次?

389096 次浏览

SnackbarContentWrapper你需要改变

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

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

这样它只在您单击时触发操作。

或者,您可以将 SignInContainer中的 handleClose咖喱到

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

这是一样的。

我怀疑问题在于你在函数组件主体中立即调用状态设置器,这迫使 React 再次调用你的函数,使用相同的道具,最终再次调用状态设置器,触发 React 再次调用你的函数..。诸如此类。

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

相反,我建议您使用三元组有条件地设置 state 属性的默认值,这样就可以得到:

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

综合演示

看看这个 CodeSandbox.io 演示的一个全面的演示,它的工作,加上破碎的组件,你可以切换之间的两个。

您必须在 onClick 中链接一个事件。另外,click 函数必须接收该事件

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

我也有同样的问题,解决方案是我没有在 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>
)
}

在像下面这样调用 handleFunction 之前,您需要添加一个事件:

function SingInContainer() {
..
..
handleClose = () => {
}


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

可以通过在函数内部使用钩子来防止此错误

在我的情况下,其原因是因为错误的 attribute名称 onblur={setFieldTouched('firstName')} -->onBlur={()=>setFieldTouched('firstName')}。后更正属性名称错误消失

我认为你可以做到这一点。在我的地方,它的工作没有任何问题。

const handleClose = (reason) => {
if (reason === 'clickaway') {
return;
}
setSnackBarState(false);
};
<SnackBar
open={open}
handleClose={()=>handleClose(r)}
variant={variant}
message={message}
/>

据我所知, 看起来这个错误是由我们调用这么多函数造成的。

大部分是 setState()

解决这个问题的一种方法是在函数内部设置状态,而不是像 onClick那样直接调用 setState

像这样使用大量的状态可能会导致多次对重新呈现做出反应,从而导致这个错误。

请参阅是否已包含 preventDefault ()。在我的情况下,我忘记使用,后来我发现这个错误。

调用函数时添加事件如下所示

之前

 <button onClick={ decrementCount() }>-</button>

after

 <button onClick={ () => decrementCount() }>-</button>

我也遇到过类似的问题,但我的情况不同。 useEffect(()=>{},[])使我避免了这个错误。

只要记住,如果传递一个带括号的函数,那么该函数将在每次组件呈现时执行。为了防止这种情况,请尝试将 ()=>放在函数的前面。

当你传入 setState 时,你可能会立即调用它,确保你的 没有设置是这样的:

Variable = { setState ()}

相反,应该这样做:

Variable = { setState ()}

有时候,函数中有 setState,当传递函数时直接调用函数,这会导致这个问题。