Live data from Hacker News

Why React Re-Renders

joshwcomeau.com

101–110 of 168 posts

Re: Why React Re-Renders

#101

I sometimes wonder what people are using React for, that they wouldn’t know this or have figured it out along the way. Let me kind of explain, as best I can, without code. We have a complex software product, an integrated compliance and risk management system with embedded workflow, automatic highlighting of potential risks due to non-compliance, plans of actions (aka risk management plans), RBAC, ABAC (used to contr…

You can get away with a lack of understanding about these things and still create beautiful products. For an example just look at Josh’s website. Beautiful react and design execution without knowing these details about rerendering.

Several things can happen when you don't understand this re-render process exactly right, among them:

- slow UIs which re-render too much. For instance, re-render of a big chunk of the component tree for each key press because you somehow found it was a good idea to pass the new value all the way up to the root of the component tree without realizing that it will re-render everything (because React will only re-render what actually changes, right?). Or, if you understand it, trusting too much the browser on its ability to run this fast. Of course, if your workstation is quite beefy and you work with small data just to test if it works during development, and are used to slowness from your OS, you may not notice the issue at all until a user with a regular computer attempts to write a bigger text than what you ever tested on your beefy machine. And of course React pushes for managed components; performance, memory usage and GC pressure be damned.

- "a second ago" "helpfully human-friendly" indicators that stay "a second ago" forever, because nothing ensures a re-render when necessary, or because the wrong value is passed / stored.

- jumps and erratic scroll behavior (on unsuspected/non-ununderstood so seemingly random re-renders), because you are building the UI declaratively but some stuff actually need procedural solutions, and doing this in React can be difficult, and you end up with difficult to understand buggy workarounds. Think adding a new blog post at the top of a blog post list that the user is currently reading, or a new message that gets added at the bottom of a message list. Yes, I know, CSS anchors are supposed to solve this, but no, you can't always use that, by the way they are still unsupported by Safari. The fact that everything is asynchronous does not help at all by the way, you can't just position things right after you add them to the DOM and right before the next visible paint like you could with vanilla JS.

I'm not making these things out off my ass.

React is sold as something that makes the lives of frontend developers easier, and many frontend developers find it compelling because of this, but in reality it's way more complex and add way more complexity than probably many people suspect / notice. Especially if you add Redux, which is surprisingly action-oriented in this declarative world - quite jarring, and also quite complicated to understand (just let me increment this counter or add this element to this list already, I don't want to deal with these freaking "reducers"! Why can't I deal with these values like in the rest of the React app, by using some kind of setState function?). Svelte got this right. The store is reactive and not action-based, very easy to understand. Also, why such an essential feature isn't provided by React itself?

React is cute and give the impression to help manage complex UIs for which vanilla JS would supposedly be a mess, but if it is not perfectly mastered, I'm yet to be convinced that this is true. You need to have a really good intuition on how React works to design a complex UI with it, probably to the point to be able to write a small prototype of React.

The reality is that React is hard to understand, but it's easy to not notice this.

Re: Why React Re-Renders

#102
post #82

Earlier quoted context omitted.

Not quite, having to use for current everywhere is just annoying, and it doesn't allow for a one-time constructor function.

Why do you have to use current everywhere? You can do const { current: myStableValue } = useRef(computation); Not sure what you mean by one time constructor function, but you can pass the result of a function to initial value. const { current: myStableValue } = useRef((() => { //called once })());

This is incorrect. useRef takes an initialValue argument, but it does not treat functions as lazy initializers like useState does. If you pass a function to useRef, you’re just going to get that function as the initial value.

If you want to lazily initialize a ref, you need to manually check if it has been initialized and run your expensive code if it hasn’t. Dan Abramov provides what appears to be a pattern officially recommended by the React team: https://github.com/facebook/react/issues/14490#issuecomment-...

Re: Why React Re-Renders

#103

Earlier quoted context omitted.

Why do you have to use current everywhere? You can do const { current: myStableValue } = useRef(computation); Not sure what you mean by one time constructor function, but you can pass the result of a function to initial value. const { current: myStableValue } = useRef((() => { //called once })());

This is incorrect. useRef takes an initialValue argument, but it does not treat functions as lazy initializers like useState does. If you pass a function to useRef, you’re just going to get that function as the initial value. If you want to lazily initialize a ref, you need to manually check if it has been initialized and run your expensive code if it hasn’t. Dan Abramov provides what appears to be a pattern official…

It's not a function, it's an immediately-invoked function expression.

Edit: I guess it would be a bit inefficient to invoke the IIFE every render just to initialize the value once. Probably better to use useMemo with [] as the dependency list then, either one achieves the same result.

Re: Why React Re-Renders

#104
from the article:

> If a component has a bunch of props and not a lot of descendants, it can actually be slower to check if any of the props have changed compared to re-rendering the component. (I don't have a source for this claim, but I've seen prominent developers like Dan Abramov make this case on Twitter)

It might vary from application to application but this seems like something that could be tested.

Also on the other side of the coin, you have memo all the things: https://attardi.org/why-we-memo-all-the-things/

Re: Why React Re-Renders

#105

Josh's content is always very very high quality. I look forward to him releasing his online React course [0] so that I can recommend it to others who are starting out. My personal biggest not-total-comprehension is around Hooks / effects. I've followed tutorials, used them in production, etc. I'm comfortable using them but I also consider them a bit of a black box, which I don't like (e.g. I'm not sure how they're im…

Hooks compose, whereas side effects and memoized values sprinkled through component constructors and lifecycle methods do not. For example, the equivalent of useEffect required calls inside of componentWillMount, componentDidUpdate, and componentWillUnmount. You try and make something like this re-usable and you’ll be leaking details of your implementation across the whole component via inclusion in these lifecycle m…

I wish the current detractors would look back at this.

The options with life cycles are either huge life cycles with interspersed features OR super painful composition. I've been in big codebases with both and it was absolute hell.

Hooks aren't perfect or pretty but the fact that they can encapsulate AND compose makes them a million times better.

Those codebases are much easier to deal with now. Again, not perfect, but much better.

Re: Why React Re-Renders

#106
post #97

Earlier quoted context omitted.

> Like, an componentDidMount event is a now useEffect with no second argument? Doesn't useEffect with no second argument run after every render? componentDidMount's equivalent is for an empty dependency array in the second argument?

Bingo. And this is the situation we are in. Even knowing basically what's going on and still getting it wrong with just the bare minimum choices of ,_ or ,[]. Mix in actual deps coming from other hooks, changing as the components re-render, and multiple layers of "custom hooks" with more of the same and it's like trying to hold back a tsunami.

They could've easily added useOnMount, useOnRender, etc

It doesn't matter that they are sugar. Just like useState is built on useReducer, it would be massively helpful to simplify and clarify what's going on.

Re: Why React Re-Renders

#107
post #93

Earlier quoted context omitted.

> But it is tremendously wasteful to make a complete copy of a large data structure! You don't make a complete copy: you make a shallow copy of the spine, and then only descend along the fields you actually modify. The number of operations scales as O(branching factor * depth), not O(size).

> You don't make a complete copy: you make a shallow copy of the spine, and then only descend along the fields you actually modify. Sounds painful.

[deleted]

Re: Why React Re-Renders

#108
post #97

Earlier quoted context omitted.

Bingo. And this is the situation we are in. Even knowing basically what's going on and still getting it wrong with just the bare minimum choices of ,_ or ,[]. Mix in actual deps coming from other hooks, changing as the components re-render, and multiple layers of "custom hooks" with more of the same and it's like trying to hold back a tsunami.

They could've easily added useOnMount, useOnRender, etc It doesn't matter that they are sugar. Just like useState is built on useReducer, it would be massively helpful to simplify and clarify what's going on.

Sure but these functions would be trivial to define in one's own code base if they so desired.

Re: Why React Re-Renders

#109

Earlier quoted context omitted.

This is incorrect. useRef takes an initialValue argument, but it does not treat functions as lazy initializers like useState does. If you pass a function to useRef, you’re just going to get that function as the initial value. If you want to lazily initialize a ref, you need to manually check if it has been initialized and run your expensive code if it hasn’t. Dan Abramov provides what appears to be a pattern official…

It's not a function, it's an immediately-invoked function expression. Edit: I guess it would be a bit inefficient to invoke the IIFE every render just to initialize the value once. Probably better to use useMemo with [] as the dependency list then, either one achieves the same result.

I missed that you were immediately invoking the function expression. But yes, that doesn't save you from running the expensive calculation every subsequent render.

Re: Why React Re-Renders

#110

Josh's content is always very very high quality. I look forward to him releasing his online React course [0] so that I can recommend it to others who are starting out. My personal biggest not-total-comprehension is around Hooks / effects. I've followed tutorials, used them in production, etc. I'm comfortable using them but I also consider them a bit of a black box, which I don't like (e.g. I'm not sure how they're im…

Hooks feel like a good system that stopped right before it became pretty. Like, an componentDidMount event is a now useEffect with no second argument? But a componentWillUnmount event is now a useEffect, with no second argument, that returns a callback? It's powerful, and it's not that hard to use, but it's cryptic and random. Why call it useEffect instead of some other more meaningful phrase? I mean, "componentDidMo…

I totally agree about the cryptic-ness when it comes to useEffect and its usage, but the neat thing is that you can trivially write your own thin wrappers with better names! I think `useEffect` is the best name possible given how broad its use cases are, because it's literally for any function side effect.
Post reply on HN