Too many re-renders when trying to change array of components on click on React

Viewed 208

I'm new to React and I'm trying to use hooks in order to generate a list of components, then when a button is clicked, change that list of components from another one with different parameters. I'm not sure of what I'm doing wrong. I've read the documentation of React and checked in StackOverflow. I'm getting an infinite loop, to many re-renders.

But as I understand it, first I set a value for generateComponents() which returns a list of components under the name of menu, then I use menu to render that list of components, but if I press the button then it will toggle changeMenu which will change the menu variable to generateComponents() with another parameter, which also should update the components...

Thanks for your patience!

function Menu(props) {
    const [menu, changeMenu] = useState(generateComponents("chivito"));

    function generateComponents(i) {
        return data[i].map((i) => (
            <Col key={i.key}>
                <Food
                    title={i.title}
                    subtitle={i.subtitle}
                    price={i.price}
                    url={i.url}
                    key={i.key}
                />
            </Col>
        ));
    }

    return (
        <>
            <button
                type="button"
                onClick={changeMenu(generateComponents("pack"))}
            >
                Chivito
            </button>

            <Container>
                <Row>{menu}</Row>
            </Container>
        </>
    );
}

export default Menu;
3 Answers

When you do onClick={changeMenu(generateComponents("pack"))} you’re invoking changeMenu immediately, during render, which causes a state update, causing a re-render, causing them to be called again, causing a state update, causing a re-render…

onClick should be a function and you’re giving it the result of calling the function. Try onClick={() => changeMenu(generateComponents("pack"))} instead.


It might be easier to see the distinction if we isolate it from the markup and jsx. Consider the following click handler function:

function clickHandler () {
  return 'banana'; // arbitrary return value
}

You're effectively doing this:

const onClick = clickHandler();
// onClick is now 'banana'

As opposed to this:

const onClick = clickHandler;
// onClick is now the clickHandler function itself

The JSX equivalent would be:

<button onClick={clickHandler}>

The problem with this, of course, is that you can't pass arguments.

There are a few solutions to the argument-passing problem, but the most straightforward is to create a new anonymous arrow function that invokes the handler with particular arguments:

const onClick = () => clickHandler(arg1, arg2, arg2)
// onClick is a new function that just calls the handler with arguments

Applied to your code, it looks like this:

<button onClick={() => changeMenu(generateComponents("pack"))}>

Alternatives you probably won't want to use:

You could also bind the arguments or create a function that returns a function, but ultimately you end up in the same place and as you can see below the anonymous arrow function is easiest.

const onClick = clickHandler.bind(null, arg1, arg2)
// new function that will receive arg1 and arg2 when invoked
function makeHandler(arg1, arg2) {
  return function () {
    clickHandler(arg1, arg2);
  }
}

const onClick = makeHandler('foo', 'bar');
// new function that calls clickHandler('foo', 'bar')

When render trigger state change by onClick causing a re-render and to it infinity looop.

so just change this line to :

            <button
                type="button"
                onClick={() => changeMenu(generateComponents("pack"))}
            >
                Chivito
            </button>

I'm not sure but this will work i guess.

function Menu(props) {
    const [menu, changeMenu] = useState("chivito");

    function generateComponents(i) {
        return data[i].map((i) => (
            <Col key={i.key}>
                <Food
                    title={i.title}
                    subtitle={i.subtitle}
                    price={i.price}
                    url={i.url}
                    key={i.key}
                />
            </Col>
        ));
    }

    return (
        <>
            <button
                type="button"
                onClick={changeMenu("pack")}
            >
                Chivito
            </button>

            <Container>
                <Row>{() => generateComponents(menu)}</Row>
            </Container>
        </>
    );
}

export default Menu;
Related