未捕获的不变违规:重新渲染过多.React 限制渲染次数以防止无限循环 [英] Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop

查看:27
本文介绍了未捕获的不变违规:重新渲染过多.React 限制渲染次数以防止无限循环的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试添加一个零食栏,以便在用户登录与否时显示一条消息.SnackBar.jsx:

从react"导入React;从道具类型"导入道具类型;从类名"导入类名;从@material-ui/icons/CheckCircle"导入 CheckCircleIcon;从@material-ui/icons/Error"导入ErrorIcon;从@material-ui/icons/Close"导入 CloseIcon;从@material-ui/core/colors/green"导入绿色;从@material-ui/core/IconButton"导入图标按钮;从@material-ui/core/Snackbar"导入 Snackbar;从@material-ui/core/SnackbarContent"导入 SnackbarContent;import { withStyles } from "@material-ui/core/styles";常量变体图标 = {成功:CheckCircleIcon,错误:错误图标};const 样式 1 = 主题 =>({成功: {背景颜色:绿色[600]},错误: {背景颜色:theme.palette.error.dark},图标: {字体大小:20},图标变体:{不透明度:0.9,marginRight:theme.spacing.unit},信息: {显示:弹性",alignItems:中心"}});函数 SnackbarContentWrapper(props) {const { 类,类名,消息,onClose,变体,...其他} = 道具;const Icon = variantIcon[variant];返回 (<小吃店内容className={classNames(classes[variant], className)}咏叹调描述的=客户端小吃店"消息={(<span className={classes.message}><Icon className={classNames(classes.icon, classes.iconVariant)}/>{信息}</span>)}动作={[<图标按钮键=关闭"咏叹调标签=关闭"颜色=继承"className={classes.close}onClick={onClose}><CloseIcon className={classes.icon}/></图标按钮>]}{...其他}/>);}SnackbarContentWrapper.propTypes = {类:PropTypes.shape({成功:PropTypes.string,错误:PropTypes.string,图标:PropTypes.string,iconVariant: PropTypes.string,消息:PropTypes.string,}).是必须的,类名:PropTypes.string.isRequired,消息:PropTypes.node.isRequired,onClose: PropTypes.func.isRequired,变体:PropTypes.oneOf(["success", "error"]).isRequired};const MySnackbarContentWrapper = withStyles(styles1)(SnackbarContentWrapper);const CustomizedSnackbar = ({打开,处理关闭,变体,信息}) =>{返回 (<div><小吃店锚点起源={{垂直:底部",水平:左"}}打开={打开}自动隐藏持续时间={6000}onClose={handleClose}><MySnackbarContentWrapperonClose={handleClose}变体={变体}消息={消息}/></小吃店>

);};定制的Snackbar.propTypes = {打开:PropTypes.bool.isRequired,handleClose: PropTypes.func.isRequired,变体:PropTypes.string.isRequired,消息:PropTypes.string.isRequired};导出默认的CustomizedSnackbar;

SignInFormContainer.jsx:

import React, { useState } from 'react';从 'prop-types' 导入 PropTypes;从'react-redux'导入{连接};从 '../../components/SnackBar' 导入 SnackBar;从'./SignInForm'导入SignInForm;const SingInContainer = ({消息,变体}) =>{const [open, setSnackBarState] = useState(false);const handleClose =(原因)=>{如果(原因 === 'clickaway'){返回;}setSnackBarState(false)};如果(变体){setSnackBarState(true);}返回 (<div><小吃店打开={打开}handleClose={handleClose}变体={变体}消息={消息}/><登录表单/>

)}SingInContainer.propTypes = {变体:PropTypes.string.isRequired,消息:PropTypes.string.isRequired}const mapStateToProps = (状态) =>{const {variant, message } = state.snackBar;返回 {变体,信息}}导出默认连接(mapStateToProps)(SingInContainer);

当我运行应用程序时出现此错误:

Invariant Violation:太多的重新渲染.React 限制渲染次数以防止无限循环.在不变(http://localhost:9000/bundle.js:34484:15)在 dispatchAction (http://localhost:9000/bundle.js:47879:44)在 SingInContainer (http://localhost:9000/bundle.js:79135:5)在 renderWithHooks (http://localhost:9000/bundle.js:47343:18)在 updateFunctionComponent (http://localhost:9000/bundle.js:49010:20)在开始工作 (http://localhost:9000/bundle.js:50020:16)在 performUnitOfWork (http://localhost:9000/bundle.js:53695:12)在 workLoop (http://localhost:9000/bundle.js:53735:24)在 HTMLUnknownElement.callCallback (http://localhost:9000/bundle.js:34578:14)在 Object.invokeGuardedCallbackDev (http://localhost:9000/bundle.js:34628:16)

问题是由 SnackBar 组件引起的.我使用 useState 钩子来改变小吃店的状态.我应该使用一个类和一个 componentShouldUpdate 来避免多次渲染吗?

解决方案

我怀疑问题在于您在函数组件主体内部立即调用状态设置器,这迫使 React 再次重新调用您的函数,使用相同的道具,最终再次调用状态设置器,这会触发 React 再次调用您的函数......等等.

const SingInContainer = ({ message, variant}) =>{const [open, setSnackBarState] = useState(false);const handleClose =(原因)=>{如果(原因 === 'clickaway'){返回;}setSnackBarState(false)};如果(变体){setSnackBarState(true);//这里是龙}返回 (<div><小吃店打开={打开}handleClose={handleClose}变体={变体}消息={消息}/><登录表单/>

)}

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

const SingInContainer = ({ message, variant}) =>{const [open, setSnackBarState] = useState(variant ? true : false);//或 useState(!!variant);//或 useState(Boolean(variant));const handleClose =(原因)=>{如果(原因 === 'clickaway'){返回;}setSnackBarState(false)};返回 (<div><小吃店打开={打开}handleClose={handleClose}变体={变体}消息={消息}/><登录表单/>

)}

综合演示

请参阅此 CodeSandbox.io 演示,了解其工作的全面演示,以及您拥有的损坏的组件,您可以在两者之间切换.

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?

解决方案

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.

这篇关于未捕获的不变违规:重新渲染过多.React 限制渲染次数以防止无限循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆