DEV Community

Cover image for Optimizing Functional React Components
Adam Nathaniel Davis
Adam Nathaniel Davis

Posted on

Optimizing Functional React Components

I've been writing functional React components for several years now. And while I feel that my code usually stands up to peer scrutiny, I've only recently become aware that there's a lot I could be doing that would be far more efficient.

I'm not talking about optimizing the way that I write code (e.g., writing fewer lines of code). I'm talking about optimizing the way that the code runs.

Nearly every day I encounter functional components (many of which were written by... me) that could honestly run in a much more efficient manner. But they don't, because they were written in a very... "basic" style, with little concern for optimization.


Image description

What do I mean by "inefficient"?

For the purposes of this article, I'm not talking about general inefficiencies that you may find in any code (e.g., nested loops). Nor am I talking about things that may specifically be considered inefficient in JavaScript code (e.g., unnecessary DOM manipulations). Instead, I'm talking about cases where the React team has given us the tools to optimize our code - but... we're simply not using them.

Consider the following basic example:

export const MyComponent = () => {
  const [clickCounter, setClickCounter] = useState(0);

  const onClick = event => {
    // increment clickCounter and
    // process the onClick event
  }

  const onMouseOver = event => {
    // do some mouseover stuff
  }

  const postClick = () => {
    // check clickCounter and potentially
    // do some post-click processing
  }

  const preClick = () => {
    // check clickCounter and potentially
    // do some pre-click processing
  }

  return <div>
    <h1 
      onMouseOver={event => onMouseOver(event)}
      style={{color: 'green'}}
    >
      My "Basic" Component
    </h1>
    <p style={{textAlign: 'right'}}>
      <MyButton
        onClick={onClick}
        postClick={postClick}
        preClick={preClick}
        style={{fontSize: 'bold'}}
      >
        Click here
      </MyButton>
    </p>
  </div>;
}
Enter fullscreen mode Exit fullscreen mode

This is about as basic as React gets. I have a simple component - <MyComponent/> - that's generating some simple JSX. That JSX in turn calls another custom component - <MyButton/> - that presumably provides some sort of button-wrapper functionality. The child component - <MyButton/> - allows you to pass in various callback functions based on whether you want something to be done pre-click, on-click, or post-click. It also accepts a style prop so you can style the button.

On the surface, this component doesn't seem to be too egregious. But there are many built-in features of the library that we're simply not leveraging. Here are some of the questions you should be thinking when you look at this component:

  1. This component accepts no props and seems to generate no side effects. So why isn't it memo-ized?

  2. Why are we using a state variable to track an internal value - clickCounter - that has no impact on the display?

  3. Why are we using objects to denote style attributes, knowing that those objects will be seen, every time the function is invoked, as completely new objects?

  4. Why are we allowing the helper functions to be re-defined every single time this component is called?

  5. Why are we creating a new function definition inside the mouseover event?

Image description

More to come...

I'm not going to tackle all of these issues in this article. Instead, I'm going to publish a mini-series that explains when (and why) to use memo(), useMemo(), useCallback(), and useRef(). I'm doing this because, quite frankly, in my past code, I haven't been using those features nearly enough.

Top comments (11)

Collapse
 
thethirdrace profile image
TheThirdRace

From the top of my head:

  1. Because memoization has a cost too. Unless there's something expansive in this component, you're gonna lose more performance to memoization than simply re-rendering the component as-is... If the component re-renders too many times, there's a problem upward the chain, not in this component

  2. Because that's very bad code. I don't see people creating state they don't need unless they're really poor devs, in which case no amount of good practice is gonna save them

  3. Recreating the object for styling isn't a problem per se, you're targeting an optimization that would shave maybe 5 nanoseconds... But, using inline-styles is really bad for browser performance which is actually mesurable with performance tools. Not to mention that inline styles can be high-jacked by malicious scripts and you should always block them with a Content Security Policy. You're 1000% better to import a CSS file and use classname instead. Not only does it solve the inline style performance, but it also makes your component reusable between projects.

  4. Again, unless you NEED to optimize, simply don't. You won't gain any advantage by using useCallback or other performance enhancing tools. Memoization has a cost that won't be alleviated by these helper functions unless you do something expansive, which is relatively rare...

  5. This is not a performance problem, but a bad coding practice. Even if you replaced the inline function with the function reference, performance is not gonna improve even 1 nano second... I totally agree with you that this is totally horrible syntax and show the dev has not understood how to pass functions around, but it doesn't impact your performance in the slightest in the end.

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

One of the problems is that so many examples online and in documentation show things that use these patterns - probably because they are demonstrating a different point and doing it succinctly. Of course it then ends up looking "like best practice" when it patently isn't.

Side note, and not relevant to the points you are making here, but the game programmer in me silently screams when I code React, because I'm constantly making throw away things even if I AM avoiding the pitfalls you outline. (The angel on my right shoulder is always bellowing GC AIN'T FREE - into my ear).

Collapse
 
fjones profile image
FJones

There's also a huge lack of best-practices for large react apps. It's always very contained, small examples, simple functionality. At best, there's article #374 on which folder structure to use, or the thirteenth video on how to hyperoptimize a Counter.

In fact, this is the case for most JS ecosystems: express, react, nestjs...

Collapse
 
merri profile image
Vesa Piittinen

Having been there, and also seeing others optimizing large React sites...

... it is all a waste of time. You'll get better results AND things done quicker if you swap to actually modern architecture. And with this I mean anything that will let you make the site run with as little client-side JS as possible whenever that makes sense.

My personal framework opinion leans like this:

  • NextJS 13? Nope! It is still based too much on the idea that React has to take over the entire DOM tree, and that hasn't been great for perf since the day someone did it the first time. React Server Components hasn't been the magic bullet to solve it.
  • Remix? Yes! It makes you actually use the web standards over React conveniences. If Remix otherwise serves your needs then go for it.
  • Astro? Yes! For SSG or SSR it is a very plausible option, and you can mix'n'match other than React to drive your client-side as well.

Basically any new tool that does the zero-JS by default approach is much more likely to solve large app issues better than hyperoptimizing bundles or trying to solve the huge component tree issue. Split to islands, it actually solves the perf issues. Figure out how to share state between client-JS islands instead, if that is needed in your project. It is much easier problem to solve than making that huge React tree optimized.

Collapse
 
thethirdrace profile image
TheThirdRace

What irks me the most is when I see an article that shows an example with 1 value, but you know fully well it's not gonna hold when you introduce a 2nd value...

And no matter how many times you search, you have 1 billion article for that 1 value, but none whatsoever on how to implement the 2nd or 3rd value...

An example for 1 value is the worst kind of example you can get. Always strive to have at least 2 values so we understand how to tackle multiple values in an elegant way.

Collapse
 
ronnewcomb profile image
Ron Newcomb

I also don't write components as efficiently as I could, and then I remember why: it'll rarely make a difference except making the code more difficult to maintain.

Optimize for your humans and your manager's budget. You'd probably get more perf from simply swapping in Preact.

Collapse
 
matborowiak profile image
Info Comment hidden by post author - thread only accessible via permalink
Mat Borowiak

Well you are asking why, but you are the one who wrote this example.

I have no idea why would one store data in app state an never use it. I am not sure how this has to do with optimization if that's just a simple redundancy. You would write empty for loop, and by removing it call it optimisation?

Creating empty in-line functon wrappers instead of passing down function directly? Why did you do that?

In-line styles? Yes, components (functions) run in loops so those declarations in-line should be avoided.

When it comes to handlers declarations and memoization of the component itself. You should not memoize components that are "static". Memoization in itself isn't "free", it consumes resources. If you will wrap every static component in useMemo/useCallback, you will decrease performance of your app. What you should look into instead is how React Fiber engine works, and how to help engine recognise absolute minimum that has changed and needs a re-render. Components architecture plays role, keys, etc.

Besides you are not going to prove anything regarding performance in such generic example. What you are doing here is fixing a problem that does not exist, and by doing that with memoization you wouldn't bring any imrpvement, and likely slow things down introducing redundant complexicity (MEMO ISN'T FREE). This component is NOT slow and is not impacting performance in any way even when containing some inefficiencies you yourself introduced for unknown reasons. If you claim otherwise, I'd like to see actual metrics proving your claims, because what I see in the frontend are devs throwing empty claims regarding "performance" for their own sattisfaction while react does extremely well out of the box, and majority of carlessly written React apps are running with zero hiccups. That said useMemo/useCallback has its use, but you are not even remotely close for that usecase to be needed here.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I guess you'd much rather that I add an "example" that has several thousands lines of code.

Yeah... that'll be a great read.

Collapse
 
matborowiak profile image
Mat Borowiak

Yes, but the things you have done to this component are not going to make it slower. And your supposed optimisation will show no effect.

Prove me wrong using codesanbox or something else. Feel free to write another component, prove the case exists, and let's see then how to improve it. Otherwise this is pointless - and thats my point.

I know from experience of working in alrge frontend systems, there are very rare scenarios React has difficulties to handle that majority will never stubmle upon. In general talking about "performance improvements" in frontend in a context of frameworks like React is just... I don't know. Prove the case exists, as doing unnecessery things is very much against actual engineering principals.

I honestly think once you show actual setup that may start choking 99% will realise they don't need to care about that one scenario at all.

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

AWESOME FEEDBACK!!!

Collapse
 
codeofrelevancy profile image
Code of Relevancy

Well written informative article..

Some comments have been hidden by the post's author - find out more