Неперехваченное инвариантное нарушение: слишком много повторных рендерингов. React ограничивает количество отображений, чтобы предотвратить бесконечный цикл

#javascript #reactjs #redux #material-ui

#javascript #reactjs #redux #материал-пользовательский интерфейс

Вопрос:

Я пытаюсь добавить панель закусок, чтобы отображать сообщение всякий раз, когда пользователь входит в систему или нет. 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 перехваты, чтобы изменить состояние панели закусок. Должен ли я использовать класс и a componentShouldUpdate , чтобы не выполнять рендеринг несколько раз?

Комментарии:

1. Вероятно, вы используете Webpack, и в этом случае я хотел бы обратить ваше внимание на опцию devtool , которая позволяет вам точно определить местонахождение ошибки в вашем исходном коде, вместо того, чтобы определять местоположение ошибки в вашем связанном коде, как у вас есть сейчас

2. Можете ли вы проверить, вызывается ли handleClose несколько раз и решит ли проблему изменение handleClose={handleClose} на handleClose={()=>handleClose} ?

3. @Nicholas Я попробовал это, но я получил ту же ошибку

4. у меня также была похожая проблема, но в моем случае это было из-за устаревших значений из предыдущих отображений, я передал пустой массив зависимостей в useCallback и без необходимости обновлял состояние.

5. У меня была похожая ошибка, но это было из-за api, вызов которого был неудачным, и в api были автоматические отключения.

Ответ №1:

Я подозреваю, что проблема заключается в том, что вы вызываете свой установщик состояния непосредственно внутри тела компонента функции, что заставляет 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 demo для полной демонстрации его работы, плюс сломанный компонент, который у вас был, и вы можете переключаться между ними.

Комментарии:

1. ваш ответ решает проблему, описанную в вопросе, однако переменная open всегда false

2. @para008 Я добавил всеобъемлющую демонстрацию , чтобы доказать, что это работает так, как должно.

3. Я не нахожу свою ошибку, потому что open однако для меня всегда false !!variant вначале равен false, затем становится true

4. возможно, мне следует задать это в другом вопросе. Спасибо за ваш ответ.

5. Ваш ответ решает мою проблему, спасибо!

Ответ №2:

В 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
>
  

Так что действие запускается только при нажатии.

В качестве альтернативы, вы могли бы просто использовать handleClose in SignInContainer для

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

Это то же самое.

Комментарии:

1. Спасибо, у меня работает. Это то, что я искал onClick={() => OnClose} .

2. может кто-нибудь подсказать мне, что погуглить, чтобы понять разницу между onClick ={() => OnClose} и onClick = {OnClose}? Спасибо!

3. Поиск в Google чего-либо вроде «привязки встроенных функций в react» должен привести вас туда, куда вам нужно. вам придется узнать об «этом» в js и функциях каррирования, это настоящая кроличья нора.

4. Именно то, что сработало для меня. Простой, но эффективный ответ. Премного благодарен.

5. разница между onClick={() => OnClose} и onClick={OnClose}?

Ответ №3:

Вы должны связать событие в вашем 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>
    )
}
  

Ответ №4:

При вызове функции добавьте событие, подобное этому

перед

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

после

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

Комментарии:

1. Это помогло мне, но зачем это нужно делать? Просто хочу прояснить свои концепции, поскольку я новичок в JS.

Ответ №5:

Вам нужно добавить событие, прежде чем вызывать вашу функцию handleFunction следующим образом:

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

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

Ответ №6:

Из того, что я собрал, похоже, что ошибка возникает, когда мы вызываем так много функций.

в основном setState()

Одним из способов обойти это будет установка состояний внутри функции вместо setState прямого вызова onClick , например.

Использование большого количества состояний, подобных этому, вероятно, приводит к повторному рендерингу react несколько раз, вызывая эту ошибку.

Ответ №7:

Просто помните, что если вы передаете функцию в круглых скобках, функция будет выполняться каждый раз при рендеринге компонента. Чтобы предотвратить это, попробуйте поставить ()=> перед вашей функцией.

Ответ №8:

У меня тоже такая проблема, и решение в том, что я не привязал событие в моем 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>
)
}
  

Ответ №9:

Вы можете предотвратить эту ошибку, используя перехваты внутри функции

Ответ №10:

ЭТА ОШИБКА ВЫЗВАНА ТЕМ, ЧТО ВЫ ИСПОЛЬЗУЕТЕ ПАРАНТЕЗИС ПОСЛЕ ИМЕНИ ФУНКЦИИ В ОТВЕТ.

 import React, { useState } from "react";
  

const Count = () => {
const [num, setnum] = 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()} в разделе Return

Ответ №11:

Итак, по сути, вы просто хотите убедиться, что вы вызываете свой установщик состояния в качестве обратного вызова для прослушивателя событий, чтобы таким образом функция не запускалась до тех пор, пока не будет выполнено действие.

Что-то вроде этого:

 onClick={() => stateSetterFunction()}
  

Ответ №12:

В моем случае его причина из-за неправильного attribute имени onblur={setFieldTouched('firstName')} -->onBlur={()=>setFieldTouched('firstName')} . после исправления ошибки имени атрибута исчезла

Ответ №13:

Я думаю, вы можете это сделать. У меня дома это работает без каких-либо проблем.

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

Ответ №14:

Убедитесь, что вы включили preventDefault() или нет. В моем случае я забыл использовать это, и позже я нахожу эту ошибку.

Ответ №15:

У меня была похожая проблема, однако мой сценарий был другим. useEffect(()=>{},[]) спасло меня от этой ошибки.

Ответ №16:

Вероятно, вы сразу вызываете свой setState, когда передаете его, убедитесь, что вы не устанавливаете его таким образом:

переменная={setState()}

вместо этого сделайте это следующим образом:

переменная={setState()}

иногда у вас есть setState в функции, и вы вызываете функцию напрямую, когда передаете ее, что вызывает эту проблему.