Reputation: 1821
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
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)
render
is called;
it will reach the set-state
state
changes
calling render
again
it will reach the set-state
state
changes
calling render
again
and ....
Upvotes: 0
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
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