sao
sao

Reputation: 1821

Why is this infinite loop happening in the following code? React & useState

I'm going to give you 2 versions of a component i wrote. Why does the FIRST one give me an infinite loop, while the second one works fine?

I isolated the problem but i am wondering why the logic doesn't follow 'under the hood'. No doubt some black magic from useState

//THROWS INFINTE LOOP ERROR
import React, { useState } from 'react';
import PropTypes from 'prop-types';

import { Popover, PopoverHeader, PopoverBody } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle } from '@fortawesome/pro-light-svg-icons';

const HelpIcon = (props) => {
  HelpIcon.propTypes = {
    title: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
    id: PropTypes.string.isRequired,
    children: PropTypes.node.isRequired
  };

  const [isOpen, toggleIsOpen] = useState(false);

  return (
    <React.Fragment>
      <span
        className="pointer text-body"
        id={props.id}
      >
        <FontAwesomeIcon icon={faInfoCircle} />
      </span>
      <Popover
        trigger="legacy"
        placement="left"
        isOpen={isOpen}
        target={props.id}
        toggle={toggleIsOpen(!isOpen)}{//<-----look here!!!!!!!!!!!!!!!}
      >
        {props.title !== false && (
          <PopoverHeader className="text-body bg-light">
            {props.title}
          </PopoverHeader>
        )}

        <PopoverBody className="text-xs cart__rebate_description text-body bg-white">
          {props.children}
        </PopoverBody>
      </Popover>
    </React.Fragment>
  );
};

export default HelpIcon;

...AND...

//THIS ONE WORKS
//NOTICE THE EXTRA FUNCTION THAT CALLS USESTATE, INSTEAD OF CALLING IT DIRECTLY
import React, { useState } from 'react';
import PropTypes from 'prop-types';

import { Popover, PopoverHeader, PopoverBody } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle } from '@fortawesome/pro-light-svg-icons';

const HelpIcon = (props) => {
  HelpIcon.propTypes = {
    title: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
    id: PropTypes.string.isRequired,
    children: PropTypes.node.isRequired
  };

  const [isOpen, toggleIsOpen] = useState(false);

  const toggle = () => toggleIsOpen(!isOpen);

  return (
    <React.Fragment>
      <span
        className="pointer text-body"
        id={props.id}
      >
        <FontAwesomeIcon icon={faInfoCircle} />
      </span>
      <Popover
        trigger="legacy"
        placement="left"
        isOpen={isOpen}
        target={props.id}
        toggle={toggle}
      >
        {props.title !== false && (
          <PopoverHeader className="text-body bg-light">
            {props.title}
          </PopoverHeader>
        )}

        <PopoverBody className="text-xs cart__rebate_description text-body bg-white">
          {props.children}
        </PopoverBody>
      </Popover>
    </React.Fragment>
  );
};

export default HelpIcon;

Upvotes: 0

Views: 478

Answers (3)

Mechanic
Mechanic

Reputation: 5390

it is causing infinite since changing state cause re-render in react; if your function call don't cause re-render it will work( kinda, not causing infinite loop) something like this

foo = () => console.log(9)

toggle={foo()}

but since set state will cause re-render your app stuck; here is the call-stack (kinda)

  1. render is called;

  2. it will reach the set-state

  3. state changes

  4. calling render again

  5. it will reach the set-state

  6. state changes

  7. calling render again

and ....

Upvotes: 0

CevaComic
CevaComic

Reputation: 2114

this is wrong, and it causes you the loop:

toggle={toggleIsOpen(!isOpen)} // you call the function in a loop

It should be:

toggle={() => toggleIsOpen(!isOpen)}

If you really want to use it your way you must do a double arrow function like this:

const toggle = isOpen => () => {
    toggleIsOpen(isOpen)
}

// and use it like

toggle={toggle(!isOpen)}

Upvotes: 2

Diyorbek Sadullaev
Diyorbek Sadullaev

Reputation: 477

You are instantly calling toggleIsOpen in the first example. You should always wrap it with another function if you want to call it with arguments other than toggle prop provides.

Upvotes: 0

Related Questions