React’s useState is slow[er]

Khalah Jones - Golden
5 min readAug 10, 2021

I have a theory that the newish react hooks architecture with its useState and useEffect methods may actually be slower to render items than traditional react architecture with `class Component extends React.Component`. This thought occurred to me after asking myself the question, how much code gets run on every render or re-render of a React component in either one?

React hooks architecture runs all of the setup and breakdown code on every render, as opposed to traditional react which only calls the `render` and `componentDidMount` or `componentDidUpdate` methods. Since more code is run in hooks than traditional on renders it would lead me to believe that it would take more resources and time to execute. Even if it is even slightly slower, we must ask ourselves under what scenarios is it slower, and in what ways is it faster if we want to build a web that is open for all. Even if say it were a 7ms slow down, it may not be much to you, and may even be imperceptible, but we must remember that as engineers and technical people we have some of the more powerful devices surfing the internet, not to mention most traffic nowadays is on mobile so what may be 7ms on our computers might be 100ms (using conjecture) on someone else’s computer or mobile device.

So I wrote some code to test this theory. I encourage everyone to clone it and try it themselves. But if you’re felling a bit lazy I’ll save you the rest of the article. I’ve found that the Hooks Architecture and in particular using useState, and useEffect is generally faster on the first render, but on subsequent re-renders it does take more time than the traditional react architecture, also react hooks is faster to render when you don’t use the hooks methods at all. The rest of this post will go into detail about how I engineered the tests, and why I made the decisions I did on the architecture.

If you look earlier in my post history you’ll see that I’ve tried my hand at this problem before. I simply spun up a webpage using apache, and tried to render one element right after the other on the same html page using `ReactDOM.render`. This was a very rudimentary and rushed solution. Upon further investigation I found that ReactDOM renders the first element the slowest no matter what it is (probably because of some initialization that it avoids in rerenders) and renders the second element faster. More than this, even if I just added an element to the render that allowed this initialization and then just rendered the actual test elements after (for a grand total of 3 ReactDOM.render calls) the last element seems to render faster. So I had to step away completely from this form of testing the code.

With the current Architecture I use Puppeteer, a tool I recently found and wanted to use. Puppeteer uses webkit, so, it is essentially chrome. I now also just have one Componenet, the Component we’re testing, rendered per page load, and I look at the page metrics, and specifically `ScriptDuration` to see how long the JavaScript took to execute. I spin up multiple tests in both their react hook way and their react traditional way one after the other, average and compare the timing of them. The tests include a basic Hello World, which doesn’t use state at all, and just returns a div with the text “Hello World” inside of it, and some advanced tests, like one with useState that simply counts the number of times a user has clicked a button, and one that does all of this but also includes useEffect to change the page title. All of these examples were pulled from the reactjs website examples of the hooks (links: useState, useEffect). I made some small optimizations that I would in the real world like making the state a property on classes, avoiding redundant code, and avoiding the repeated creation of functions in the render method.

These different functions are used so we can analyze where the benefits and downfalls of react hooks come in in terms of speed. As I mentioned before I made it so that it runs the test multiple times, and takes a mean. Due to the nature of rendering and JavaScript sometimes the results take really long, or are really short. So in order to really get a gauge we should run many tests and take the aggregate results. For Components that have a button, like the useState, and useEffect Components, I click the button numerous times. All of this adds up to me feeling pretty confident in making the claims made above, and the conclusions reached below.

I ran the test in production mode 100 times for each React Component in their respective formats. For the Components with buttons I clicked the button 10 times. These tests took a while to run, and again you can run them yourself by simply visiting the repo and following the instructions there. I also while playing with this, writing this article, and testing the code, have run the test multiple times and have definitely seen a pattern. Here are the results.

React hooks architecture is actually a bit slower than the traditional architecture, especially when adding useState or useEffect. On this particular run running the tests were ~3ms for the useState and useEffect hooks, however interestingly enough, hooks were ~1ms faster when building the Hello World Component. In other tests I’ve run the number of useState calls is proportional to how much longer the code takes, the more you use useState the slower it gets. Also the gap between them increases with the number of clicks and subsequent re-renders on elements, so the more you click on the button state, the slower the react hooks architecture was, and the larger the gap.

My solution has been to use hooks for simple elements and use React’s traditional architecture when the elements get a lot more complicated, and especially if that element has a lot of renders called, like elements that depend on listening for scroll, or user input, and on elements that actually use useState, and useEffect. But this of course has led me to more questions. I wonder what the speed difference is with nested elements, since that is a lot closer to the real world, I wonder about pureComponents speed vs all of these, and I wonder how custom hooks perform. I will write another article, and do more tests on these, and stop when I feel like I don’t have more questions

This rabbit hole was inspired by articles that ask why it is that apps progressively get larger and slower. I think things like this, new architecture decisions and all are how you get there. Sometimes engineer fear that if they stop making things newer and shinier, they will no longer work. I must say, if I program myself out of a job, I consider that a job well done. Computers are meant to make it so anyone can do just about anything, without the help of a technician. There will always be maintenance, and after a certain time we must ask ourselves as developers am I using this because it is new and shiny, and making my app worse or better. Programmers really need a Hippocratic oath.

--

--