Performance issue with Modals in Tests (JSDOM) #888
-
We recently build a lot of modals that contain form elements and noticed that the unit/integration tests for them show that they're really slow. Upon investigating in the browser, it seems that the Modal components get rerendered when form elements inside ModalBody changes. One slight refactor (shown below) makes the same tests go 10x faster:
I also have a small codesandbox example where you can see the rerenders using React Dev Tools: https://codesandbox.io/s/distracted-waterfall-o5moc?file=/src/index.tsx . Additional context is that we want to do validation on all the form elements + submit button. I recognize that this is an old pattern; we just haven't had the time to redesign it. That shouldn't distract us from Modal performance as we see slow tests in modals that don't contain form elements either. We use React Testing Library for our integration tests and user-event to interact with forms in the test. |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 13 replies
-
Thanks for the detailed report @ghostling! Added a ticket to investigate further into our backlog. |
Beta Was this translation helpful? Give feedback.
-
Hi again @ghostling, I had a chance to look into your slowness issue and believe I figured it out. I've created a fork of your Codesandbox with a recommended solution. Here's a breakdown of my takeaways: 1- The majority of the slowness in SlowModal is because it is handling the form state in the same component that renders the Modal wrapper (see line 20 and line 40 of SlowModal). Whenever someone types in a field, the form state updates and it re-renders the entire Modal wrapper and everything within it. Since the form state is only concerned with what's inside the Modal wrapper, we can (and arguably should) move that state inside the Modal component. I do this in the 2- The 3- A helpful nit: within the I'm going to make a ticket to update our doc site to provide more insight into this best practice. Hope this helps! |
Beta Was this translation helpful? Give feedback.
-
Hi @ghostling We finally had some time to investigate this further. Here's a write up the findings: Initially, I setup multiple CodeSandboxes to see if the test performance issue was in our implementation or the Reach/Dialog implementation. In those, I setup three modal components:
I then ran a simple render test to see how long the tests took to run. In all samples, once a modal had focusable content, the test times increased to above 1000ms. This happened in the Paste implementations and the base Reach/Dialog implementation. So I came to the conclusion that the issue was with Reach/Dialog. After that conclusion, I started down the path of swapping out Reach/Dialog for Reakit Dialog in our primitive. I chose Reakit because we were already using other Reakit components for primitives, and those have been successful. I created a PR for that and then started a PR to change the Modal to use the new primitive. I soon realized that changing to Reakit would introduce too many break changes to Modal. After discussing with the team, we came to the conclusion that we shouldn't switch to Reakit and maintain the current Modal API, and not break any downstream team trust. I then did some more digging into Reach/Dialog and found their tests ran fairly quick even with focusable content in the dialog. Then I realized we were a few versions behind on their dialog, and they were also testing their dialog with React 17. They have a method of testing their components with React 16, and after running that I found that the tests run considerably slower in React 16: https://share.getcloudapp.com/rRuGr5om (top example is React 16) After that I set down the path of testing to see if upgrading Reach/Dialog and then upgrading to React 17 would solve the performance. I tested the current Paste version, upgrading Reach/Dialog, and then upgrading Reach/Dialog and React. In each test I ran the test suite 2x. One with a modal without focusable content (top), and one with about 10 buttons and an input (bottom). Screenshots are below:
As you can see once we upgrade Reach/Dialog we get a big improvement in our test performance. 1195ms down to 344ms for the modal with focusable content. We get a very slight improvement when we upgrade to React 17 as well. For now upgrading the React/Dialog version should speed up our tests and any downstream team tests. We'll upgrade to React 17 at a later date. |
Beta Was this translation helpful? Give feedback.
-
Wow, took me a while to circle back 😅 , so the Paste upgrades did not improve the test speed very much, but we found that mocking out Modals in our tests really helps, hoping to get some feedback on this approach:
In the mock, we just made everything a div and kept role/data-s tags to make it easy to select on for presence. Propagating the props don't really do much as I found that the Example timing:
|
Beta Was this translation helpful? Give feedback.
Hi @ghostling
We finally had some time to investigate this further. Here's a write up the findings:
Initially, I setup multiple CodeSandboxes to see if the test performance issue was in our implementation or the Reach/Dialog implementation. In those, I setup three modal components:
I then ran a simple render test to see how long the tests took to run. In all samples, once a modal had focusable content, the test times increased to above 1000ms. This happened in the Paste implementations and the base Reach/Dialog implementation. So I came to the conclusion that the issue was with R…