Why React Re-Renders
joshwcomeau.com
joshwcomeau.com
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 implemented). The other (even bigger) question for me is "why" -- what prompted the React team to change everything over to "effects". Anyone?
doesn't mean you should...
It unfortunately does not, you can hve various foot guns with refs or omitting dependencies from the array argument; but to your point it makes it a lot easier to ensure you do it correctly by collocating logic related to each cross cutting concern instead of entangling the code in the lifecycle methods.
More precisely it makes it easier to follow correct patterns, but as always that is subjective and hooks can be contentious :)
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, "componentDidMount" tells you exactly what it is. Does "useEffect"?
Why should onload things be a function, but onunload should be a function returned by a function?
Why does useRef give you a thing used for attaching handles to DOM elements so you can refer to them elsewhere, AND it gives you an object whose .current property can be used as a variable that persists without causing a render?
It's like someone complained that there were two many functions in the API, and their names were too long, so they overcorrected in the opposite direction.
And they should have included a useUnloadEffect() by default, even though it's trivial to write, just for clarity. It's way too easy to miscount the number of ()s in useEffect(() => () => {}, []);
They also should have included a few other basic hooks, like useStableValue() for just computing a constant once on first render.
I hate the name `componentDidMount` though. useEffect seems much better to me.
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 })());
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-...
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.
It's called useEffect because it runs on the (side)effects observed by the dependency array. An empty array happens to happen on mount. useEffect returns a teardown function which happens to correlate with unmount when an empty array is passed.
It's called useRef because it returns a (mutable) reference that's detached from the reactive layer. The DOM element connection is just sugar.
I agree that useEffect has a lot of footguns, but this position seems very shallow. The whole pattern changed, it doesn't make a lot of sense to continue comparing the two
I believe this teardown function runs on unmount whether or not the dependency array is empty.
edit: I'm wrong, the cleanup is also called when dependencies change, just not on mount.. eg. it gets called before the effect callback being called again.
In other words, the cleanup function is called on unmount when useEffect is called with an empty array, and if useEffect has dependencies, it will also be called when those dependencies change
This kind of goes towards my point. useEffect works, and it works great. It's flexible and powerful - but it's cryptic as hell, and the interface is just "memorize it because you have to", not something more intuitive.
What if useEffect required a 2nd argument, but you had the option to pass in self, which would still offer the onMount behavior, by mirroring the way the other form of it worked? Bada-bing, now there's no exception to the rule. What if the 2nd argument was a named argument, like "trigger"? Now it's self-documenting. What if the teardown function as an optional 3rd argument with a name, too? Now it's easy to glance at a useEffect and tell whether it's declaring a teardown without carefully reading the body. etc etc.
He says "useRef offering a connection to DOM elements is just sugar", but that's not sweet, to me. There's no reason one thing should do two totally different things. That's exactly what I'm complaining about.
I definitely agree useEffect is awfully cryptic and easy to get wrong, and I'm happy to be corrected... but I'm not sure how I'm wrong about it running on every render when there's no 2nd argument?
I kinda wish they'd made it easier to do the common operations like onMount and onUnmount by providing some simplified wrappers around useEffect (with less power comes less responsibility... or something). Of course we can make custom hooks for those, but having the well-trodden paths be paved is always nice.
Of course, it does run on unmount when the dependency array is empty, and it runs on unmount when there are dependencies in the dependency array.
But upon re-reading what you said, I think your intent was that it's comparable to componentWillUnmount() in class-based React only when the dependency array is empty (because otherwise the cleanup function also gets called when dependencies change).
My apologies, as I never really used class-based React, so the distinction was lost on me (and also forgot about the cleanup function being called between useEffect callback calls)
If anything it’d be weirder to make this two separate concepts. What would they even be? What does a ref not already do, as a box that contains an arbitrary value, that we’d want from a box with an element in it?
For many projects it felt like it gave you super powers, for some it was just OK, and a small percentage of interfaces just didn't fit the React model and were better some other way.
It appears to me that since then that small percentage has gotten the development attention (understandably), creating a React which is more well rounded and broadly useful but there is more to learn and the super power feeling has been dulled a bit.
Overall it has improved but I still wonder what a React that was more specialized for those interfaces where it really works great would be like.
Doesn't useEffect with no second argument run after every render? componentDidMount's equivalent is for an empty dependency array in the second argument?
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.
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.
Hooks, and useEffect, was supposed to be designed in a way to eliminate that class of bugs.
It might help to have a simple mental model for them?
Picture there is a global variable called _currentComponent:
_currentComponent: {
previousValue: React.Element
hooks: HookValue[]
currentHook: number
firstRender: boolean
}
Each HookValue is whatever that hook wants. For useState something like: HookValue = {
hookName: 'useState',
value: [state, setState],
}
Before your component is run, the renderer sets `_currentComponent` to your component with its hooks set to their current values.If you run `useState(initial)` it looks like this:
function useState(initialValue) {
const component = _currentComponent
let hookValue;
if (firstRender) {
hookValue = {
hookName: 'useState',
value: [initialValue, (newValue) => {
hookValue[0] = newValue
markForUpdate(component); // some React-provided function to mark this as needing an update
}
}
component.hooks[component.currentHook++] = hookValue;
return hookValue.value
} else {
hookValue = component.hooks[component.currentHook++]
assert(hookValue.hookName == 'useState')
return hookValue.value
}
Surely not perfect, but totally useable as a model of what's going on. To be honest I wish React showed pseudocode like this in the tutorials, it makes it a lot easier for me to understand.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 methods, not to mention any data you’re shoving onto the component instance. But it’s still doable.
Now, what if you wanted to use this re-usable behavior inside of another re-usable behavior? It gets complicated fast! Now your library needs to expose the lifecycle-updating methods of the underlying library, leaking details all the way down. Hooks are opaque from the perspective of lifecycle, while still having access to all the same… hooks.
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.
Driven by the power of our 'ingenuity', even the most elegant constructions will find themselves drifting towards this horizon. It takes super human effort to resist the effect, and super humans to keep systems from escaping us.
Any suggestions for a "second best" course that is available now?
Instead it’s used with JavaScript, where you have to constantly force yourself to create your prop objects in a very specific way to avoid or force (re)rendering.
It’s a constant struggle to control re-rendering. I still can’t believe React became as big as it is.
Vue and Angular just fit JavaScript better.
Immutable?
But React in JS is a blessing – it brought good thinking to messy world. And as a direct influence, JS will adopt immutable data structures – tuples and records.
Vue works similar to React + MobX, but with weird design decisions like implicit reactivity, and a stale ecosystem thanks to the Vue2 vs Vue3 debacle.
I like the explanation on what triggers rendering in this post. Props and state are usually used in the explanation for this part, but props actually only matter if you want to use React.Memo. And the one part every React developer should know is that in the absence of React.Memo all children will rerender if any state changes.
and even then you mostly don't need to care either except in extreme situations (like useCallback is not really necessary most of the time0
A workaround is to not make copies of large data structures. Just modify the large object directly. Then just call setState({}); That's right... setState() with an empty object triggers a re-render. Now you don't even have to store the object being modified in state. You can hold the large object in a member field of the class. So even though your component is stateful, you are not telling React what your state fields are - you are managing it yourself. At this point, React's programming model has broken down.
Immutable updates do in fact require that if you want to update `state.some.nested.field`, you have to make copies of _all_ objects in that path: `nested`, `some`, and `state`. This isn't unique to React.
Yes, class component `this.setState()` lets you get away with mutations. That's not really a _good_ thing. If anything, it's a holdover from an earlier era of JS, where it was much more common to use React with data structures that might be mutable.
With the `useReducer/useState` hooks, the React team explicitly designed them to require immutable updates and pass in new references, otherwise they'll bail out, assuming that since it's the same reference nothing was changed and no render is needed.
Some more details:
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
Another alternative is the "immutability-helper" library, which was originally published by the React team as "react-addons-update". It's a lighter library with less proxy magic going on, but at the cost of being more explicit: instead you create an object describing how to update an immutable value to produce a new immutable value. I've used it in the past in a few places but mostly recommend Immer over it now.
I had catalogued _dozens_ of immutable update libs between 2015-2018, and Immer is simply superior to all of them.
Which is why Immer is a non-negotiable part of RTK, and something we specifically tell everyone they should be using with Redux:
- https://redux.js.org/style-guide/#use-immer-for-writing-immu...
- https://redux-toolkit.js.org/usage/immer-reducers
(in fact, I recently got quoted on the top of the Immer docs with a tweet I wrote saying how awesome Immer is :) https://immerjs.github.io/immer/ )
I agree that having the ability to construct objects/arrays that are compared by nested values, rather than references, will be useful.
But, given that Immer already exists, and only meaningfully requires ES6 Proxy support, I'm not sure how much of an impact the availability of Records will have.
In the case of RTK specifically... Immer is built into `createSlice/createReducer`, and it's _always_ getting called even if you manually create an immutably-updated result yourself. So I don't see any real change or benefit there.
If you think React has anything to do with Functional Programming, please read this ASAP: https://mckoder.medium.com/why-react-is-not-functional-b1ed1...
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).
It works out cause I only use state of this complexity in a single spot. The code to do the copying does not read well in my opinion, and it definitely could get nasty in some cases (slower systems with updates on each keystroke say).
A library like Immer seems very reasonable if you are working on big nested structures often. Worth considering depending on the case.
Sounds painful.
I don't think I could recommend doing it the way you describe because there are a huge number of ways it can go wrong
What I've found interesting is how many developers think a React "component" (which since hooks is just a function) has some special privileges or abilities that a normal JS function does not. Like, whether it will be selectively executed or what variables are created anew versus reused between subsequent calls. It seems unclear that a React component is just a function, and displays all the behavior expected in a plain old function.
While I agree it was hard to know when React would re-render in the old, class component paradigm, it seems much easier to know when a function will re-render, since it has to re-render whenever the function is called.
It _is_ true that when you call a React function component it "runs its code" just like any regular old JS function. But when that function gets run and what all the side effects of its code are actually is quite complex.
The only real complexity (for the developer) is the use of hooks, effects etc. if you don't mess with useMemo (which you generally shouldn't). Certainly they aren't pure functions, they have side effects and are stateful, and that has some nuances, but (kudos to the React team) once you understand hooks as a reference to the instance value and a setter for that value, they're pretty easy to understand.
I guess I don't personally find thinking of them as a class as that useful, my mental model of "it's just a function with some external references (via hooks)" gets me there.
why? is this not the primary way to re-init expensive internal component state when specific props change?
> I think as developers, we tend to overestimate how expensive re-renders are. In the case of our Decoration component, re-renders are lightning quick.
Certainly it's there for a reason, and you may have expensive operations, but in my experience developers reach for useMemo much too early and often, and it just adds complexity to their functions. The cost of checking the parameters for changes adds overhead that may be more expensive than just re-doing the "expensive" operation. My rule of thumb is if the operation is less than O(n) where n < ~5000 I don't reach for useMemo.
There have been some benchmarks done on this, and when it pays off to use.
In general, I think it's always a mistake to tell people "don't use this tool because you may shoot yourself in the foot", best to explain the when and why. But I do have a problem when the tool has just stupid defaults that make it harder to use correctly in the first place.
Building on Josh's bit about the UI tree (It's not about the props) [1], just separate the counter and the decoration in to separate sub trees. Doing so leads to nicer component structures anyway.
[0] - https://overreacted.io/before-you-memo
[1] - https://www.joshwcomeau.com/react/why-react-re-renders/#its-...
> 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/
beginners don't know enough to do optimisation seniors know enough that they only optimise what is needed mid-level are capable of build code that needs to be optimised, but they tend to over-optimise and often do the optimisation wrong
for example what a mid-level might do:
const C1 = React.memo(({values}) => {...}) const C2 = () => { return <C1 values={[1,2,3]} /> }
That React.memo is useless because arrays and objects are mutable in JS It also reveals a flaw in JS/React in that memoization often needs to happen at the parent instead of the child, creating awkward code and implicit behaviour and implicit performance traps when forgetting to memoize object/array props that the child expect to be memoized. The child decides which props should be memoized, but doesn't enforce it and no amount of typescript will catch that problem
Fortunately there is a proposal that will fix this problem by introducing records and tuples like python (immutable objects/arrays) https://github.com/tc39/proposal-record-tuple but it still seems far off to being usable as I don't think it will be viable to polyfill it for browsers that don't support it
Consider that a decade ago, people who were starting with the frontend were learning jQuery, which is almost irrelevant now.
It's not that simple. At the time, jQuery was absolutely pivotal in bringing about ES5 and the transpilation revolution on the front end. More or less its' entire API was subsumed by the browsers, and so jQuery became unnecessary, but far from irrelevant.
I can see the same thing happening today with React/JSX. There is simply no better way of expressing a UI than JSX-like components. And FRP as a paradigm for UI development is here to stay. So the future of web dev probably looks something like Yew [0].
It's a pretty terrible idea if you're going to do it in a business setting, as the original author(s) will always be it's Achilles tendon, making the project a liability before it even goes into production.
Most projects use react or angular because it makes onboarding new members easier, and these frameworks really aren't as bad as some people on hn claim.
That's the origin of both react(Facebook) and angular(google) after all.
I can't speak for the specific companies you listed, but the number of times I've heard someone sing praises about a homegrown UI framework at their place of employment is approximately zero. The sentiment expressed about those is generally hatred and agony.
That's not to say it can't be done well, but most don't. Also, a company being able to hire/onboard engineers does not imply that their onboarding process is smooth, or that their house-made framework is well designed.
I am deeply puzzled by both your and the sibling comment, which suggest that the only way to go is to build a framework. To advance such argument, especially when comparing something to React, is to forget that:
- React also for a long time was advertised as a view-layer library for creating UI components, not as a "framework".
- There've been numerous debates in which advocates of Angular or Ember were suggesting that because of such inherent lack of structure, React apps were always different between projects, as opposed to the clear conventions used in Angular or Ember project. This did not deter React supporters and did not prevent React from succeeding.
- React was created as a library when web browsers did not have a standardized component model; just as jQuery was created as a library when browsers did not have a unified way of interacting with the DOM. Years have passed, and web browsers have matured to the point when a native component model has become a reality. You do not need to home-grow a framework in order to take advantage of them.I've seen people do abominations like each web component is a React root, message passing systems on the side for complex objects... better use React directly
This is not true at all, at least not enough to be able to completely replace React (or any frontend framework) with native APIs. You still need some sort of high-level abstraction(s) to tie everything together.
As for web components, I'd rather not. Nothing about it works for me. Not the class-based decorator syntax. Not the CSS scoping. Not the use of custom elements. The prop syntax is awful and the paradigm just feels cumbersome.
I work with Angular (its component model is close enough) and have read the Lit docs. Never will I choose that option.
A function is just a better way to write UIs.
For example, if you destroy and re-create DOM all the time, then it loses critical information like user's cursor position and text selections. It is also slow to read and write to the DOM. Thus the need for an intermediate data structure, the virtual DOM.
React re-renders the virtual DOM every time the state changes. And it then diffs the previous and current ones against each other and does just the minimal number of DOM mutations to sync it up. To speed this up a bit, array elements are denoted with "key" so (I assume) there is a way to see if an element has been added or deleted.
But re-rendering the virtual DOM all the time can also be costly in terms of performance. Thus the next set of optimizations: React.memo+immutable data, and so on..
YMMV but I the only time I've ever had to really think about this stuff was when trying to frankenstein legacy jQuery code into a React app.
(In fact, I'll go further than this and add that the section on "Escape Hatches" should be re-read by senior/lead engineers, as many have misconceptions due to learning concepts ad-hoc from code of mixed quality: https://beta.reactjs.org/learn/escape-hatches)
I chose react as I had a large application to make and I knew react had 2 critical libraries I wanted to reuse. Having now learnt react and the underlying ideas, I think Svelte (https://svelte.dev/) may solve the general problem better. However it has less libraries/documentation and community support. If I had more time to learn I would have considered learning it as a potentially superior solution.
It's quite high quality and the learning environment is great: you're in a live code editor + hear and see the teacher's code/cursor movements.
Absolutely not what I need.
What I want to know is how to build things with React as performant as possible. This discussion of complex and performant apps seems elusive.
If you're confused about the last part where it mentioned where useMemo and useCallback can help when passing props, I completely understand after reading Kent C Dodds block and Dan Abramov.
Further, I learned a lot by taking interactive course from Dan Abramov in https://justjavascript.com
Highly recommended! good read!!
https://kentcdodds.com/blog/usememo-and-usecallback https://overreacted.io/a-complete-guide-to-useeffect/
Related, a couple years back I wrote a post on the same topic: "A (Mostly) Complete Guide to React Rendering Behavior" [0]. It's longer and has more details, but fewer diagrams :) (Josh's ability to make interactive posts is amazing.)
I originally wrote my "Rendering Behavior" post specifically because the existing React docs didn't clearly spell out this kind of behavior, and I was _constantly_ seeing questions that showed people didn't understand these concepts well. For example, many folks assume that "React re-renders my component when its props change", when in fact the real answer is that "React re-renders recursively by default, _regardless_ of whether or not any props changed". There's also a lot of confusion over how Context ends up affecting renders, and I've seen folks end up in situations where setting state in an "context provider" parent component ends up rendering the _entire_ app - not because Context changed, but because they did a `setState()` and that's the natural behavior. So, I was trying to help clarify those sorts of nuances.
I can say that it's been one of the top couple posts I've written in terms of traffic and positive feedback (along with my "Redux vs Context differences" post).
The good news is that the new React beta docs [1] _do_ cover at least some of this rendering info, although it's more contextual in various points of the explanations and in less detail. I'm hopeful that after the main tutorial/API reference material is done and the docs are opened up for external contributions, that we can help add a couple pages that cover some of this rendering behavior info as well.
There's a few other good articles I've also seen covering this topic as well [2] [3] [4].
[0] https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
[2] https://www.zhenghao.io/posts/react-rerender
[3] https://alexsidorenko.com/blog/react-render-cheat-sheet/
[4] https://www.developerway.com/posts/react-re-renders-guide
As in, I literally have a todo list entry that I keep bumping back until "Next Sunday", because other stuff is higher priority. (like, trying to get Redux Toolkit 1.9 wrapped up and shipped... and also playing as much golf as possible while the weather is decent :) )
The shortest answer is that it's still effectively the same - the one major change is that React 18 will now batch _all_ updates in _any_ given event loop tick, regardless of whether those were queued up inside a React event handler or not.
There's a good discussion on this in the React 18 Working Group post on "Automatic Batching":
Does that mean if i have a redux store attached to my app it rerenders everything when i change something small in the store? Because the store is usually one of the top most HOCs.
<Context.Provider value={contextValue}>{children}</Context.Provider>
And `children` behave diferently. `children` is a prop and thus not considered as an element of the Provider component, but of the App component [2]. Meaning the components inside `children` will rerender when App renders, and not when Provider renders.
1: https://github.com/reduxjs/react-redux/blob/master/src/compo...
2: https://www.developerway.com/posts/react-elements-children-p...
Look up Mark Erikson's blog[1], who's a Redux maintainer, for a lot of details on Redux's internals (acemarke on HN), he also answers tons of questions everywhere[2] about Redux, he helped me so much understand it!
[1] https://blog.isquaredsoftware.com/ [2] https://stackoverflow.com/a/40386189
Instead, components directly call `store.subscribe()`, and check to see if they need to update after each dispatched action.
See my post "A (Mostly) Complete Guide to React Rendering Behavior" for more details:
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
If the DOM tree stays but a property of some element changes, if that property affects size or layout or other visual state of things, the browser will re-render the changed element, along with any dependent elements. Could be many elements when re-layout is needed, could be just one if the only change is color.
Probably unpopular opinion: manually call `render` is always better.
Creating your own `ui = f(state)` is very trivial with DOM event, custom element, and a whatever 5kb pure view lib. With event delegation technique and a global store, I can build almost whatever ui with this pattern.
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 control different things), etc.
The backend does most of the work. What gets presented at the frontend can be complex.
The React component model gives us an almost functional DSL that we use for compact, highly expressive tooling that allows us to integrate common look and feel, table exports, etc., across some really complex data.
We started before Hooks were widely available or widely known, and, we started with class-based components, because we realized pretty quickly we were going to have to do some funky state management, especially where workflow was involved (we want a common look and feel across processes and tasks, so workflow tasks and processes get wrapped in higher level components that call back to other high level components; the forms can have dozens or hundreds of variables, some interdependent, so state has to be communicated up for validation; React handles sending it down).
Our key state management function is a custom signal handler that gets passed as a prop to all sub components, then, via a common wrapper, back up to the high level components that group everything for display and consistency purposes.
As we refined this handler (and a few others), we moved from class-based to functional components.
The functional components are simpler than the class-based components they replaced, with much of the complexity located in one place, the handler.
Our handler allows us to pass state up “just far enough”, and React handles updating all affected components.
Performance is fantastic, validation is easy(ish; it takes some staring to grok how it hangs together), and debugging (once one has grokked, is straightforward(ish; there are edge cases that cause pause, until the “oh, yeah, that” moment).
That functional DSL has allowed us to build several suited-for-purpose DSLs, e.g., our workflow system, which, while not no code, is low code (configuration as code more than anything else).
If we didn’t understand the React state management model, none of this would have been possible.
So I ask, in naïveté, what are people building that they didn’t need to know that?
(We are likely to move to Hooks anytime soon, because we’ve already solved the problem they were introduced for, and without having to rewrite much. Hooks look like we would have to make wholesale changes, in which we see little value, at least right now, OMMV in the future).
The article is for beginners (the article itself says its intended audience beginner-intermediate, but I'd say it leans very much towards beginner). Thus this is precisely an example of how people who use React would learn this. It would be odd to comment on every explanation of a concept that everyone would have already learned that concept.
It could have been ever so much shorter and clearer. But I’ve realized I really am becoming a get offa my lawn type as my 60s near.
Ah, Usenix and the early web, I miss thee. ;-)
We’ve taken a different approach, we’ve written a pure js engine which computes and manages state based on operator used to express logic, and then have a recursive render loop in react which provives engine with update hooks to it uses to rerender components when it should. That way we can very handle complex state logic and then update with ease all without dealing with passing state up and down.
We still have a few ideas on how to further optimize which will be built in future versions. But already we, and the OS community are building some advanced apps using Lowdefy
I’ll be checking out that repo, thanks!
- 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.
Would love to better understand why the site is pulling in the Babel transpiler in production?
Edit: siblings know more than me, thank you siblings!
I know that Vercel/Next.js will lazily load in certain JS chunks but there seems to be some mechanism that is loading a ton of stuff up front. Edit: from other comments in this thread, it's the sandbox code, really crazy how much overhead that adds to the page!
I tried a few things to speed it up, but eventually gave up and did the UI in plain old Javascript. It runs a hell of a lot better, but the code does feel a lot more flimsy.
Edit: Here's a demo of the UI I built https://www.youtube.com/watch?v=Wt71bNYe3qc
> Can pigs run fast? Domestic pigs can run as fast as 17 km/h while wild pigs can reach a speed of 30 km/h!
I do like understanding my code as much as possible. So I was choosing between: 1. Understand React better, and reading about the "React" way to use these mouse events. 2. Doing it in VanillaJS
The great thing about the "React" way of doing things is that it's just the JavaScript way of doing things.
React can be summed up entirely as: "a function that takes in props and returns rendered HTML". It is not a framework. There is no black magic. There are no idioms. There are no batteries included.
Anything else you do with it beyond that is entirely up to you.
> I think as developers, we tend to overestimate how expensive re-renders are. In the case of our [pure] component, re-renders are lightning quick.
> 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.
Edit: Sorry, because the parent component re-rendered too. Except it didn't. But maybe? Nah, it didn't. Did it?
A lot of people are going to think this is being ridiculous, but there were actually bugs as recently as a few months ago(maybe still?) in the dev tools where they would not know the actual re-render reason and it would spit out a generic "parent component re-rendered" instead.