4

I'm using react-spring to animate a Modal based on @reach/dialog. The Modal can have any children. In the children I'm fetching some data based on some prop.

The problem is that the fetch call is made two times on opening the modal. I think it has probably to do with how I'm managing the state and that is causing re-renders.

I'v tried memoizing the children inside the modal and it didn't work, so I think that the problem is outside of the Modal component.

Here is something close to my code and how it is working https://codesandbox.io/s/loving-liskov-1xouh

EDIT: I already know that if I remove the react-spring animation the double rendering doesn't happen, but I want to try keeping the animation intact.

Do you think you can help me to identify where is the bug? (Also some tips on good practice with hooks are highly appreciated).

  • it doesn't get called twice, the api request makes it look like it is being called twice. – Junius L May 29 '19 at 16:23
  • @JuniusL. It's not that it's called twice, I think that is the rendering that happens more than once. If I profile the Modal with the React Devtools it shows a total of 5 renderings in the Modal component across the cycle of opening and closing. – Joaquín Romero Franco May 29 '19 at 16:28
  • I tried using React.memo on the child but the result it's the same @filipe . What could be different with React.PureComponent? – Joaquín Romero Franco May 29 '19 at 17:08

3 Answers3

1

I checked and it is rendered twice because of animation in a Modal component when an animation is finished, modal is rendered second time when I commented out fragment responsible for animation, Modal renders only once.

 const Modal = ({ children, toggle, isOpen }) => {
  // const transitions = useTransition(isOpen, null, {
  //   from: { opacity: 0 },
  //   enter: { opacity: 1 },
  //   leave: { opacity: 0 }
  // });
  console.log("render");
  const AnimatedDialogOverlay = animated(DialogOverlay);
  // return transitions.map(
  //   ({ item, key, props }) =>
  //     item && (
    return (
        <AnimatedDialogOverlay isOpen={isOpen}>
          <DialogContent>
            <div
              style={{
                display: `flex`,
                width: `100%`,
                alignItems: `center`,
                justifyContent: `space-between`
              }}
            >
              <h2 style={{ margin: `4px 0` }}>Modal Title</h2>
              <button onClick={toggle}>Close</button>
            </div>
            {children}
          </DialogContent>
        </AnimatedDialogOverlay>
    );
  //     )
  // );
};
kwdowik
  • 56
  • 4
  • Thanks for your time and for your response, sadly I already knew this, I'm hoping to find a solution that keeps the animation. I'm going to edit the question to better reflect that – Joaquín Romero Franco May 29 '19 at 16:53
1

it renders three times because your return component has transitions.map since you have three item inside the

    from: { opacity: 0 }
    enter: { opacity: 1 }
    leave: { opacity: 0 }

the {children} was called two times when the isOpen is true you can fix the issue with just removing the from: { opacity: 0 } and leave: { opacity: 0 }

so change your modal.js => transitions

  const transitions = useTransition(isOpen, null, {    
    enter: { opacity: 1 }
  });
Srinivasan Raman
  • 441
  • 1
  • 4
  • 7
  • As you said the problem was that it rendered the children every update. To mantain the animation I finally used the state variable that the transition holds and ``{state==="update" && children}`` . Now I only have to check how to make the Height change on enter / leave. Thanks!!! – Joaquín Romero Franco May 29 '19 at 18:41
1

The problem is, that at the end of the animation AnotherComponent remounts. I read similar problems about react-spring. One way could be, that you lift out the state from AnotherComponent to the index.js. This way the state will not lost at remount and you can prevent refetching the data.

const AnotherComponent = ({ url, todo, setTodo }) => {
  useEffect(() => {
    if (todo.length === 0) {
      axios.get(url).then(res => setTodo(res.data));
    }
  });
....
}

Here is my version: https://codesandbox.io/s/quiet-pond-idyee

Peter Ambruzs
  • 7,763
  • 3
  • 30
  • 36
  • I really like this solution, but I can't extract the logic out of the children because he is the responsable for the fetching ( in the actual code he receives an ID to do the fetching). But thanks anyways. It will probably help me to think an abstraction. – Joaquín Romero Franco May 30 '19 at 13:03