-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior #38
Comments
Original author: Anujit Nene @anujitnene Totally loved reading this consolidated article, nailed the concepts to clarity! One doubt I have - In the example of context provider (the one before the optimized example using MemoizedChildComponent), if there's a setState call in the ParentComponent, will there be two renders of the subtree scheduled - one due to the setState itself and another one due to the change in the context value provided to the provider due to this setState? Is this understanding correct? |
Original author: BS-Harou @bsharou This is really nice summary, thank you for putting it together! In regards to using memo/PureComponents everywhere: It's possible that trying to apply this to all components by default might result in bugs due to cases where people are mutating data rather than updating it immutably. In my experience, the decision to use memo/PureComponents everywhere goes together with enforcing immutability of all props passing through React and so if someone is mutating such a value it is a mistake on behalf of the developer/code reviewer/bad types rather than the arch. decision itself. As a reader I also feel a bit confused on whether you think memoizing everything is a good idea or not where on one hand you argue you in most cased don't have to and you should think about each case but but on the other you say that using it everywhere is probably overall net positive. I would personally argue that using it every time everywhere is probably the way go. Of course by every time I don't mean when a developer is prototyping or playing with code locally or putting together some examples, but I am talking about full blown production SPAs. Also in regards to "everywhere" I guess there are some exception, so I guess it is fair to say everywhere but Though again you might be able to argue that using it even in these cases might be better for consistency, so that you don't forget it somewhere important and junior devs don't accidentally copy paste the component and forget about it. |
Original author: fabb Thanks a lot for the insights, I learned a few more details! I have one suggestion and one detail question. First the suggestion. You mentioned this: This is not entirely true, there are a few implementations of useContextSelector which only cause rerenders when the selected part of the context changed (using `observedBits`), and there is even an RFC open to integrate it into core React: https://github.com/reactjs/... Second the question. Suppose we pass an onClick function down like in your example, but without a memoized child component, and it‘s assigned to the onClick of a <button> component: function ParentComponent() { return <button onclick="{onClick}"/> Does it make a difference when we use useCallback for onClick, or pass down onClick directly? As far as I have understood it, the render phase would rerender the same components in both cases, but the commit phase is different, as without useCallback, the onclick handler of the button html element in the dom would need to be updated on every render. Is this true? Is this performance-relevant and warrant the use of useCallback? Thanks, |
Original author: AndyYou @andyyou About `Memoize Everything?` section. I don't really get the meaning "only if it's going to make a difference in behavior for the child". Is that means new reference will make child component get different result? |
Original author: Gadi Tzkhori @gadi_tzkhori isn't contextValue as an object, recreated every rerender?, thus requires useMemo wrapping? |
Original author: Ganesh Pendyala @Ganeshlakshmi Many Thanks for putting this together Mark. It really hardened my understanding of the React rendering behaviour and the pitfalls while using Context. |
Original date: 2020-05-30T15:41:19Z That's exactly the point I'm trying to make throughout the article. Yes, in general, you probably want to memoize your context values, but there's other factors that play into whether or not the rest of the components render. If you don't have anything else blocking renders between the parent that renders the context provider, and the child that consumes the context, the child will always render anyway due to the default recursive rendering behavior. |
Original date: 2020-05-30T15:43:01Z Sort of. Multiple components may be flagged as "dirty" and needing to be re-rendered, all in one event tick. React will then iterate over the entire tree during a single batched render pass. Any component flagged as dirty will definitely re-render, and React will recursively re-render any children of those components |
Original author: Dennis Cual @denniscual It means that there's no point to memoize data like function if in the first place, it can never help and could just add some little overhead because of memo process. Like if you use the function object to the "host components" like div because it doesn't render anything than to itself. Or passing not memo function object to a Component but the perf doesn't affect. |
Original author: Dennis Cual @denniscual Imo, referencing the unstated solution, in the official React documentation, to this blog is not a good choice like you said the "observedBits" because theres a possibility that it would change in the future. And about your question in button onClick handler, in React reconciliation process the button will be the same but it will only mutate the onClick prop. It means that engine will not destroy the button element rather will reuse the same button element then update onCLick handler which is not the expensive at all and will not lose some dom state like focus, etc. |
Original author: Benjamin S. @benjamin_such Hey @markerikson:disqus, really great article! What I really struggled with was/is the memoization of objects. A classic example would be something like:
In the past I thought this would help, but it doesn't (obviously now) since shallow comparison does not realize it's the same object and will recalculate `mergedConfig`. I feel like this approach is wrong, because I don't see a solution except extracting every key from `customConfig` and put it into the dependency array which sounds absolutely horrible lol. Can you help me in this regard? May I ask how you gathered all that knowledge? Was your initial motivation just pure interest in React and thus read all the code? I'm really curious how you approach learning all this, maybe I can take something with me :P |
I think I the answer on my own, which is: There is no way to memoize objects and make sure to memoize values coming from |
Really useful |
Good read. Thank you. |
Thanks for the guide. It's great! |
Thank you so much for that. What cause the difference between the 2? |
Hey Mark, I'm really really appreciate all your awesome works on software engineering. I got a question when reading the following paragraph: What I unstanding is that you wanna us to be attentive to writing Does my unstanding is correct? |
@YagamiNewLight : not quite. If we have: function Parent() {
return <MemoizedChild><OtherComponent /></MemoizedChild>;
} In that case, However, each time The real point here is that if you use |
Pardon me please.. I don't get why If the |
@YagamiNewLight: remember that JSX is transformed into const el1 = React.createElement(OtherComponent);
const el2 = React.createElement(OtherComponent);
console.log(el1, el2); // {type: OtherComponent}, {type: OtherComponent}
console.log(el1 === el2) // FALSE - they are different references So, every time |
OK, I get it. Many many thanks for your patient reply!!! |
Firstly great article Mark and thanks for this. I have one doubt. You wrote "React-Redux uses context to pass the Redux store instance, not the current state value". |
@aqarain You can think of the redux store instance as just a global plain object which can be read from anywhere, but the change of it won't cause any rerenders of the UI. So here comes the So yes, the store instance is one big object which is a value, but not |
@YagamiNewLight |
Great Article! I have one question though, how will state updates work in React 18? How will they batch all setState calls? Will they use microtasks to do that? |
@tanthongtan: reactwg/react-18#21 covers the overall intended behavior. As far as I know, yes, they're using microtasks or something to enable running all queued updates at the very end of the event loop tick. |
Thank you for this wonderful article, @markerikson. I have a question about the following statement:
I am not quite seeing this behavior in my app. Here's the top level code from my sample repo:
The My app has a button that toggles the view state. When I run this app in the React DevTools profiler and click on this button, only the following part of the tree gets re-rendered:
Notably the App and its other children (HomePage, Header etc.) are not being re-rendered. I have not used |
@nareshbhatia I'm going to guess that your provider component looks like this: function ViewStateContextProvider(props) {
const [someState, setSomeState] = useState(whatever);
const contextValue = {someState, setSomeState};
return (
<MyContext.Provider value={contextValue}>
// KEY PART HERE
{props.children}
</MyContext.Provider>
)
} That causes the "same element" optimization that I talked about to kick in, and React will stop recursing as soon as it sess that |
@markerikson, you hit the nail on the head. I had missed that completely!!! Thank you for clarifying. |
@markerikson Love this article! Feels like my itchy and annoying but not reachable point in my back is finally scratched! Just one little question though.
Did you use the same "setCounter1" on both buttons to show that memoized element won't change? |
Magnificent! Thank you so much for taking the time to write such a detailed review. This has been very helpful to me and I will bookmark this for future reference. Thanks!! |
This is a really excellent resource, thanks for writing it down! If I may offer a few suggestions, there's a few specific topics I would be interested in if you could explore them further:
|
@arendjr : good questions!
|
@markerikson Thanks a lot, I’ll check those out! Update: really loved these examples of |
thanks a lot. this article covers a lot of details about react |
Love this article, thanks a lot! |
Veritable 「the guy who writes longest blog article」. Anyway, great article. Thanks. |
Hey, @markerikson. In the
According to the docs, both An excerpt from the docs:
|
@bernardobelchior : hmm, that's a good point. I keep forgetting those get double-run now. The point still stands in general in that renders will run more often, but that does make this harder to log in effects as well. |
No problem, just wanted to make sure I understood everything correctly. |
Hey, @markerikson. I have a big confusion of As you mentioned on Also mentioned about And lets see useDeferredValue example of React blog:
The question being that how it is possible that while input is updating and we can see its changes, the other component (suggestions) has a previous value ? I mean React will apply change into the DOM when it makes sure that all rendering is completed but here we see that We can see new changes on the browser when |
@mohammadsiyou : yeah, in that case React is going to run two different render passes + commit phases. It runs one render phase with the "old" data, and commits the UI with those changes, then runs a second render pass with the "new" data and commits that. |
Hey! @markerikson I still don't get why a specific component is getting rerendered even though the value that is being used is primary and identical. I am going to paste here the codesandbox that I created if you don't mind. https://codesandbox.io/s/react-rerender-0x7nb2?file=/src/App.tsx |
the double negatives here are a bit confusing |
Hi! I want to experiment a bit, and now I cannot understand the reason why the number becomes 5 instead of 1 after the first click. I know "state updates may be asynchronous", but why it's scheduled so that the outer export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(n => {
setNumber(5);
return n + 1;
});
}}>Increase the number</button>
</>
)
} |
@markerikson Thank you for such an effort! Can you clarify, please, these parts:
What do you mean by 'original event handler call stack' ? call stack that exists in particular event loop tick ? If so, 'in a totally separate event loop call stack' probably means call stack in totally separate event loop tick.. Or there are different queues completely ? |
No description provided.
The text was updated successfully, but these errors were encountered: