0
votes

I have a parent functional components that uses useState hook to set state (the fact that this issue is in hooks should be irrelevant, because I experienced the same behaviour when using class component) and it needs to set state about some information that it gathers when rendering children, but when the state is set, it rerenders the children again causing an infinite loop

export const SomeConst = (props) => {

  const [information, setInformation] = useState([]);
  const newInfo = []

    const reportBoundingRectWithIndex = (index) => {
      return (width, left) => {
        newInfo[index] = { width, left };
        setInformation(newInfo);
      };
    };

  return (
    {children.map(child => (
        <ChildComponent
            reportBoundingRect={reportBoundingRectWithIndex(index)}
            />
        ))}
    )}

Child components rendered has this in useEffect (optionElement is created using useRef):

useEffect(() => {
const elementInfo = optionElement.current.getBoundingClientRect();
props.reportBoundingRect(elementInfo.width, elementInfo.left); });

This is only a simplified version of the code, I added this const newInfo to gather this new array of information, without it the width and left info gets lost from memory, because the setState is asynchronous and waits for more changes (but when it actually gets called, this information is no longer then and gets get to undefined in this array)

I tried this in a stateful components but the result with componentDidMount and setState and everything is the same, I would really like to get some pointer about how said behvaiour should be achieved

Any help is very much appreciated

1

1 Answers

0
votes

So the problem in your code is that your useEffect in child should not be called on every update otherwise this will lead to an infinite loop as the within useEffect you call a parent function that updates state and causes a re-render which again triggers the useEffect

export const SomeConst = (props) => {

  const [information, setInformation] = useState([]);
  const newInfo = []

    const reportBoundingRectWithIndex = (index) => {
      return (width, left) => {
        newInfo[index] = { width, left };
        setInformation(newInfo);
      };
    };

  return (
    {children.map(child => (
        <ChildComponent
            reportBoundingRect={reportBoundingRectWithIndex(index)}
            />
        ))}
    )}
  )
}

and in child call useEffect only on initial render(or some change)

useEffect(() => {
   const elementInfo = optionElement.current.getBoundingClientRect();
   props.reportBoundingRect(elementInfo.width, elementInfo.left);
}, []);