169

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?

Josh Pittman
  • 7,024
  • 7
  • 38
  • 66
Slim
  • 5,527
  • 13
  • 45
  • 81
  • 1
    You're probably using Webpack, in which case I'd like to bring to your attention the [devtool](https://webpack.js.org/configuration/devtool/) option which allows you to locate precisely the error in your original code, instead of locating the error in your bundled code as yyou have now – Nino Filiu Mar 20 '19 at 16:25
  • 11
    Can you check if `handleClose` is called multiple times and if by changing `handleClose={handleClose}` to `handleClose={()=>handleClose}` will solve the problem? – Nicholas Mar 20 '19 at 16:27
  • @Nicholas I tried it but I got the same error – Slim Mar 20 '19 at 16:50
  • i also had similar issue, but in my case it was due to stale values from previous renders, i had passed empty dependency array in useCallback & was updating state unnecessarily. – Wasit Shafi Aug 04 '21 at 19:53
  • I had a similar error but that was due to an api whose invocation was failing and there were automatic retires on the api. – Yug Singh Jan 17 '22 at 16:19

17 Answers17

271

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.

GregL
  • 37,147
  • 8
  • 62
  • 67
  • 2
    your answer resolves the problem described in the question however the variable `open` always `false` – Slim Mar 20 '19 at 17:08
  • @para008 I added a comprehensive [demo](https://codesandbox.io/embed/kwly8y4l0v?fontsize=14) to prove that it works as it should. – GregL Mar 20 '19 at 17:45
  • I don't find my mistake because `open`always false for me however !!variant is false at the beginning then it becomes true – Slim Mar 20 '19 at 18:37
  • maybe I should ask it in another question. Thank for you answer. – Slim Mar 20 '19 at 19:14
  • @para008 Check out the updated demo. It should fully work now, and simulate when `variant` is not set. – GregL Mar 20 '19 at 19:20
  • 2
    Your answer resolve my problem, thanks! – Patrick Martinus Jan 02 '22 at 07:54
140

In SnackbarContentWrapper you need to change

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

to

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

So that it only fires the action when you click.

Alternatively, you could just curry the handleClose in SignInContainer to

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

It's the same.

Ben Steward
  • 2,338
  • 1
  • 13
  • 23
Josh Pittman
  • 7,024
  • 7
  • 38
  • 66
10

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>
    )
}
Elyson Romeiro
  • 101
  • 1
  • 3
7

While calling function add event like this

before

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

after

 <button onClick={ () => decrementCount() }>-</button>
smit agravat
  • 223
  • 2
  • 9
4

You need to add an event, before call your handleFunction like this:

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

return (
    <SnackBar
        open={open}
        handleClose={() => handleClose}
        variant={variant}
        message={message}
        />
    <SignInForm/>
  )
}
Ravindra S. Patil
  • 11,757
  • 3
  • 13
  • 40
Cristian marín
  • 319
  • 2
  • 3
3

From what I've gathered, It seems like the error is caused when we call so many functions.

mostly setState()

One way around this will be to set the states inside a function instead of calling the setState directly by onClick for example.

Using a lot of states like this probably causes react to re-render multiple times, causing this error.

kushagra-aa
  • 540
  • 5
  • 11
3

Just remember if you passed a function with parentheses, the function would execute every time the component renders. To prevent this, try to put ()=> in front of your function.

goodhyun
  • 4,814
  • 3
  • 33
  • 25
2

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>
)
}
Harsh Patel
  • 6,334
  • 10
  • 40
  • 73
2

You can prevent this error by using hooks inside your function.

2

So basically, you just want to make sure that you're calling your state setter as a callback to an event listener, so that way the function doesn't fire off until there's an action.

Something like this:

onClick={() => stateSetterFunction()}
1

I had a similar issue, however my scenario was different. useEffect(()=>{},[]) saved me from this error.

Hayeul Tanjib
  • 311
  • 3
  • 7
1

THIS ERROR IS BECAUSE YOU ARE USING PARANTHESIS AFTER FUNCTION NAME IN RETURN.

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

} export default Count;

DONT USE ::: onClick= {increment()} or onClick= {decrement()} in Return Section

Akash27
  • 11
  • 3
1

just one tip , always call function upon some action like this onClick{()=>func()} and not onClick{func()} as the latter might hit up re-render issue due to multiple calls

0

In my case its cause because of wrong attribute name onblur={setFieldTouched('firstName')} -->onBlur={()=>setFieldTouched('firstName')} . after correcting attribute name error gone

Mohd Qasim
  • 896
  • 9
  • 20
0

I think you can do this. At my place it works without any problem.

const handleClose = (reason) => {
  if (reason === 'clickaway') {
    return;
  }
  setSnackBarState(false);
};
<SnackBar
    open={open}
    handleClose={()=>handleClose(r)}
    variant={variant}
    message={message}
    />
tdy
  • 36,675
  • 19
  • 86
  • 83
0

See That you have included preventDefault() or not. In my case, I forgot to use that and later I find that mistake.

0

You're probably calling your setState right away when you pass It in, make sure you're not setting it like this:

variable={setState()}

instead, do it like so:

variable={setState()}

sometimes, you have your setState in a function and you're calling the function directly when you pass it in which causes this issue.

Ash
  • 21
  • 2