React's render() Doesn't... Render
Tags: react, javascript, functional, rendering
Published: 2020-04-25 19:21:24

My recent foray into functional components has made me realize that there's a lot of confusion out there about the React rendering cycle.  I'm not pointing a general finger at anyone _else_.  I'm raising my hand and acknowledging my own misconceptions.  I've been doing React development now for years, but I'm still finding places where my knowledge has been... _lacking_.

## Naming Things Is Hard

React devs talk a lot about _rendering_ and the _rendering cycle_ and, if you're looking at a class component, the `render()` function.  The problem with these terms is that they all imply _an action_.  Specifically, they imply that something will, well... you know... be _rendered_.  But that's not necessarily the case at all.  And misunderstanding the distinctions can be detrimental to our work.

This might be one area where the naming convention embedded in class components is, if not _harmful_, at least, _obtuse_.  I say this because every single class component in React _must_ have a `render()` function defined.  If there is no `render()` function, the class won't compile as a React component. 

Maybe that doesn't strike you as a problem, but think for a moment about how we typically name our functions.  And think about what is typically implied by those names.  To illustrate this, take a look at these actual function names that are drawn from one of my React projects:

```javascript
const deleteUser = (userId = '') => { 
  // function logic here 
};

const getRows = () => { 
  // function logic here 
};

const sortUsers = (column = '', direction = '') => { 
  // function logic here 
};
```

You don't need to understand anything about my app to know what these functions do.  The function names clearly _tell you_ what happens when you call them.  

But there's another truth that we can imply when we see functions like these.  The understanding is typically that this functionality will do what the name implies it will do _every single time we call that function_, and _only when we call that function_. 

In other words, we don't need to wonder "How many times will a user be deleted?"  The answer is, "As many times as the `deleteUser()` function is called."  

We don't need to worry about whether we are needlessly sorting-and-resorting the users.  All we need to do is find anyplace in the app where `sortUsers()` is being called.  Because the users will be sorted _whenever_ `sortUsers()` is called, and _only_ when `sortUsers()` is called.

Now let's look at something that we see in every single class component:

```javascript
export default class Yo extends React.Component {
  render = () => {
    return <div>Yo!</div>;
  }
}
```

As simple as this may look, it kinda _breaks_ our universal, fundamental understanding of exactly how functions _work_.  Don't believe me?  Well, consider these points:

1. Calling `render()` doesn't necessarily return _anything_.  _Inside_ the guts of React, I'm sure it's reasonable to state that the `return` statement is executed every single time `render()` is called.  But from the perspective of someone who doesn't live _inside_ the React engine, this function usually won't return _anything at all_.  In fact, since the component is stateless and the content is static, the `return` statement really only returns anything _once_ during its entire lifecycle, even though it may be called _repeatedly_.

1. Which leads to my second point: Exactly how often will `render()` be called, anyway?  _Who the hell knows???_  In a React application, it can be virtually impossible to know exactly _when_ this `render()` will be called and _how often_ it will be called.  That's because it's tied to the component _lifecycle_.  In a React application, you never call `render()` directly.  And yet, `render()` gets called repeatedly, for every component, sometimes in use-cases that are hard to fully understand.

1. Although this is somewhat semantic, "render" doesn't really describe what the `render()` function is actually doing.  And I believe this accounts for at least some of the confusion.  In my book, "render", in a web-based application, means something like, "I'm painting something on the screen."  But there are many times that calling `render()` can result in _no updates whatsoever_ being painted to the screen.  So, from that perspective, it would probably have been clearer if the required `render()` function were, in fact, called something like, `checkForRenderingUpdates()`, or `renderIfContentHasChanged()`.  Because that's much more akin to what it's _actually_ doing.

## Greater Clarity(???) With Functions

Does this get any "better" or "cleaner" if we switch to functional components?  Umm... maybe??  Consider the functional equivalent:

```javascript
export default function Yo() {
  return <div>Yo!</div>;
}
```

On one hand, we've removed the ambiguity of that `render()` function because there _is no `render()` function_.  On some level, that's "good".  

But I've noticed that this doesn't do much to clarify developers' understanding of how React is checking for updates.  In fact, it has the potential to further obfuscate the process because there simply is no built-in indication, _inside the component definition_, that spells out just how-or-when this component is being re-rendered.  

This can be further muddied because functional components come with none of the traditional "lifecycle methods" that we had at our disposal in class components.  You can say what you want about lifecycle methods - and sometimes they can be an absolute _pain_ to deal with.  But the only thing worse than managing component lifecycle with the lifecycle methods of class components, is trying to manage lifecycle processes in functional components - which have no lifecycle methods.  And at least, when you had those lifecycle methods at your disposal, they served as a tangible marker of the component's native lifecycle.

This is where I sometimes find functional components to be _more confusing_, and _more obtuse_, than class components. I've already talked to a good number of functional-programming fanboys who stridently believe that:  If a functional component is _called_, then it is also _rendered_.  But this simply isn't true.

It _is_ true that, every time you call a functional component, the rendering _algorithm_ is invoked.  But that's a far cry from saying that the component is _rerendered_. 

## Static Components

Let's look at where the rendering conundrum causes a lot of confusion:

```javascript
export default function App() {
  const [counter, setCounter] = useState(0);
  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>Increment ({counter})</button>
      <Child/>
    </div>
  );
}

function Child() {
  console.log('Child has been called');
  return (
    <div>
      I am a static child.
      <Grandchild/>
    </div>
  );
}

function Grandchild() {
  console.log('Grandchild has been called');
  return (
    <div>I am a static grandchild.</div>
  );
}
```

We have three layers in our app:

`<App>` &rarr; `<Child>` &rarr; `<Grandchild>`

`<App>` is a stateful component.  It holds and updates the `counter` value.  `<Child>` and `<Grandchild>` are both pure components.  In fact, they're both _static_ components.  They accept no input, and they always return the same output.  Although they're both descendants of `<App>`, they have no dependencies upon `<App>`, or `<App>`'s `counter` variable - or upon anything else for that matter. 

If you plopped `<Child>` or `<Grandchild>` into the middle of any other app, at any particular location, they'd do the exact same thing - every time. 

So here's where it seems to me like there's still a lot of confusion out there.  What happens when you click the "Increment" button??  Well, it goes like this:

1. The `counter` state variable inside `<App>` gets updated.
1. Because there has been a change to `<App>`'s _state_, `<App>` rerenders.
1. When `<App>` rerenders, `<Child>` is called.
1. `<Child>`, in turn, calls `<Grandchild>`.

But here's where things get sticky.  The rerendering of `<App>` will result in `<Child>` being _called_.  But does that mean that `<Child>` was _rerendered_???  And will calling `<Child>`, in turn, lead to `<Grandchild>` being _rerendered_???

The answer, in both cases, is: No.  At least, not in the way that you might be thinking.

(BTW, I put the `console.log()`s in this example because this is exactly what I've seen other people do when they're trying to "track" when a given component is rendered. They throw these in, then they click the "Increment" button, and then they see that the `console.log()`s are triggered, and they say, "See.  The entire app is being rerendered every time you click the 'Increment' button."  But the `console.log()`s only confirm that the component is being _called_ - not that it's being _rendered_.)

In this demo app, people often say that, "The entire app is being rerendered every time you click the _Increment_ button."  But at the risk of sounding like a "rules lawyer", I would reply with, "What _exactly_ do you mean by 'rerendered'??"

## _Reconciling_, Not _Rerendering_

According to the React documentation on Reconciliation, this is what's basically happening when a `render()` is invoked:

> When you use React, at a single point in time you can think of the `render()` function as creating a tree of React elements. On the next state or props update, that `render()` function will return a different tree of React elements. React then needs to figure out how to efficiently update the UI to match the most recent tree.

(You can read the full documentation here: https://reactjs.org/docs/reconciliation.html)

Of course, the explanation above implies that there _are_ differences in the before-and-after trees of React elements.  If there are no differences, the diffing algorithm basically says, "do nothing".  

For this reason, I almost wish that React's `render()` function was instead renamed to `reconcile()`.  I believe that most devs think of "rendering" as being an active process of drawing/painting/displaying elements on a screen.  But that's not what the `render()` method does.  React's rendering cycle is more like this:

```javascript
const render = (previousTree, currentTree) => {
  const diff = reconcile(previousTree, currentTree);
  if (!diff)
    return;
  applyDOMUpdates(diff);
}
```

This is why it can be a misnomer to imply that a static component is _ever_ truly "rerendered".  The render process may be _called_ on the static component, but that doesn't mean that the component will truly be "rerendered".  Instead, what will happen is that the React engine will compare the previous tree with the current tree, it will see that there are no differences, and it will bail out of the render process.

## DOM Manipulation Is Expensive, Diffing Is Not

You may see this as an inconsequential distinction.  After all, whether we call it "rendering" or "reconciling", there is still _some sort of_ comparison/computation being run every single time that we invoke the render cycle on a component.  So does it really matter if the reconciliation process short circuits before any real DOM manipulation can be applied??

Yes.  It matters.  _A lot._

We don't chase down unnecessary rerenders because our computers/browsers are so desperately constrained that they can't handle a few more CPU cycles of in-memory comparisons. We chase down unnecessary rerenders because the process of DOM manipulation is, even to this day, relatively bulky and inefficient.  Browsers have come lightyears from where they were just a decade ago.  But you can still drive an app to its knees by needlessly repainting UI elements in rapid succession.

Can you undermine an app's performance merely by doing in-memory comparisons of virtual DOM trees?  I suppose it's technically _possible_.  But it's extremely unlikely.  Another way to think of my pseudo-code above is like this:

```javascript
const render = (previousTree, currentTree) => {
  const diff = quickComparison(previousTree, currentTree);
  if (!diff)
    return;
  laboriousUpdate(diff);
}
```

It's almost always an unnecessary micro-optimization to be focused on the `quickComparison()`.  It's much more meaningful to worry about the `laboriousUpdate()`.  

But don't take _my_ word for it.  This is directly from the React docs, on the same page that explains the Reconciliation process (emphasis: _mine_):

> It is important to remember that the reconciliation algorithm is an implementation detail. React could rerender the whole app on every action; **_the end result would be the same_**. Just to be clear, rerender in this context means calling `render` for all components, it doesn’t mean React will unmount and remount them. It will only apply the differences following the rules stated in the previous sections.

## Conclusions

Obviously, I'm not trying to say that you shouldn't care about unnecessary rerenders.  On some level, chasing them is part of the core definition of what it means to be a "React dev".  But _calling_ your components **_is not the same as rendering_** your components.  

You _should_ be wary of unnecessary rerenders.  But you should be careful about the term "rerender".  If your component is being _called_, but there are no updates made to the DOM, it's not _really_ a "rerender".  And it probably has no negative consequences on performances.