All Chapters - Advanced React deep dives, investigations, performance patterns and techniques (Nadia Makarevich)

0.0(0)
Studied by 0 people
call kaiCall Kai
learnLearn
examPractice Test
spaced repetitionSpaced Repetition
heart puzzleMatch
flashcardsFlashcards
GameKnowt Play
Card Sorting

1/101

encourage image

There's no tags or description

Looks like no tags are added yet.

102 Terms

1
New cards

A component doesn’t render when its props change

Normal React behavior is that if a state update is triggered, React will re render all the nested components regardless of their props. And if a state update is not triggered, then changing props will be just "swallowed": React doesn't monitor them.

In the context of re-renders, whether props have changed or not on a component matters only in one case: if the said component is wrapped in the React.memo higher-order component. Then, and only then, will React stop its natural chain of re-renders and first check the props. If none of the props change, then re-renders will stop there. If even one single prop changes, they will continue as usual.

2
New cards

Triggering nested components

If a component's re-render is triggered, all nested components inside that component will be re-rendered.

3
New cards

Improving Performance with State Isolation

Problem: Unnecessary re-renders in large React apps.

Solution:

  • Identify isolated state: Find state used by a limited set of components.

  • Create a new component: Encapsulate the state and its dependent components.

  • Move state to the new component: Place the useState hook inside the new component.

  • Render the new component: Use the new component in the main app.

Benefits:

  • Reduced re-renders: Only components within the new component re-render when the state changes.

  • Improved performance: Large, slow components unaffected by unnecessary updates.

  • Cleaner code: Centralized state management and better component organization.


<p><strong>Problem:</strong> Unnecessary re-renders in large React apps.</p><p><strong>Solution:</strong></p><ul><li><p><strong>Identify isolated state:</strong> Find state used by a limited set of components.</p></li><li><p><strong>Create a new component:</strong> Encapsulate the state and its dependent components.</p></li><li><p><strong>Move state to the new component:</strong> Place the <code>useState</code> hook inside the new component.</p></li><li><p><strong>Render the new component:</strong> Use the new component in the main app.</p></li></ul><p><strong>Benefits:</strong></p><ul><li><p><strong>Reduced re-renders:</strong> Only components within the new component re-render when the state changes.</p></li><li><p><strong>Improved performance:</strong> Large, slow components unaffected by unnecessary updates.</p></li><li><p><strong>Cleaner code:</strong> Centralized state management and better component organization.</p></li></ul><p></p>
4
New cards

The danger of custom hooks

If a custom hook uses state, and that hook is used within a component, updating the state within the hook will trigger a re-render of that component. This is because the component is essentially "subscribed" to the state changes within the hook.

So, where you put state is very important. Ideally, to avoid future performance problems, you'd want to isolate it as much as possible to as tiny and light components as possible.

<p>If a custom hook uses state, and that hook is used within a component, updating the state within the hook will trigger a re-render of that component. This is because the component is essentially "subscribed" to the state changes within the hook.</p><p>So, where you put state is very important. Ideally, to avoid future performance problems, you'd want to isolate it as much as possible to as tiny and light components as possible.</p>
5
New cards

Passing components as props

  • Problem: React re-renders the entire component tree when the state changes, leading to slow performance, especially in complex apps with expensive components.

  • Scenario: Imagine a scrollable content area with heavy components inside (e.g., VerySlowComponent), and a feature that requires a block to move based on the user's scroll. Attaching an onScroll handler to update the state leads to lag due to frequent re-renders.

  • Solution:

    • Extract the scroll state and moving block logic into a separate component (ScrollableWithMovingBlock).

    • Pass the "slow" components as props to this new component.

    • React only re-renders the scrollable div and moving block, not the slow components, improving performance and keeping the scroll smooth.

This avoids unnecessary re-renders for components that don’t need to change, leading to a much better user experience.

<ul><li><p><strong>Problem</strong>: React re-renders the entire component tree when the state changes, leading to slow performance, especially in complex apps with expensive components.</p></li><li><p><strong>Scenario</strong>: Imagine a scrollable content area with heavy components inside (e.g., <code>VerySlowComponent</code>), and a feature that requires a block to move based on the user's scroll. Attaching an <code>onScroll</code> handler to update the state leads to lag due to frequent re-renders.</p></li><li><p><strong>Solution</strong>:</p><ul><li><p>Extract the scroll state and moving block logic into a separate component (<code>ScrollableWithMovingBlock</code>).</p></li></ul><ul><li><p>Pass the "slow" components as props to this new component.</p></li><li><p>React only re-renders the scrollable div and moving block, not the slow components, improving performance and keeping the scroll smooth.</p></li></ul></li></ul><p>This avoids unnecessary re-renders for components that don’t need to change, leading to a much better user experience.</p>
6
New cards

What is a component

What makes a component different from any other function is that it returns Elements, which React then converts into DOM elements and sends to the browser to be drawn on the screen. If it has props, those would be just the first argument of that function.

<p>What makes a component different from any other function is that it returns Elements, which React then converts into DOM elements and sends to the browser to be drawn on the screen. If it has props, those would be just the first argument of that function.</p>
7
New cards

Components Chain

An Element is simply an object that defines a component that needs to be rendered on the screen. In fact, the nice HTML-like syntax is nothing more than syntax sugar for the React.createElement function. (React.createElement(Child, null, null)).

Parent component, which returns that definition, wants us to render the Child component with no props. The return of the Child component will have its own definitions, and so on, until we reach the end of that chain of components.

<p>An Element is simply an object that defines a component that needs to be rendered on the screen. In fact, the nice HTML-like syntax is nothing more than syntax sugar for the <mark data-color="blue" style="background-color: blue; color: inherit">React.createElement </mark>function. <em>(React.createElement(Child, null, null)).</em></p><p>Parent component, which returns that definition, wants us to render the Child component with no props. The return of the Child component will have its own definitions, and so on, until we reach the end of that chain of components.</p>
8
New cards

What does React do in “re-render“

referring to "re-render", is React calling those functions and executing everything that needs to be executed in the process (like hooks).

From the return of those functions, React builds a tree of those objects. We know it as the Fiber Tree now, or Virtual DOM sometimes. In fact, it's even two trees: before and after re-render.

By comparing ("diffing") those, React will then extract information that goes to the browser: which DOM elements need to be updated, removed, or added. This is known as the "reconciliation" algorithm.

9
New cards

Equal Objects (Elements) in re-rendering

if the object (Element) before and after re-render is exactly the same, then React will skip the re-render of the Component this Element represents and its nested components.

And by "exactly the same," I mean whether Object.is(ElementBeforeRerender, ElementAfterRerender) returns true.

React doesn't perform the deep comparison of objects. If the result of this comparison is true, then React will leave that component in peace and move on to the next one.

When elements are passed as props to a component, and this component triggers a re-render through a state update, elements that are passed as props won't re-render.

<p>if the object (Element) before and after re-render is exactly the same, then React will skip the re-render of the Component this Element represents and its nested components.</p><p>And by "exactly the same," I mean whether <em><mark data-color="blue" style="background-color: blue; color: inherit">Object.is(ElementBeforeRerender, ElementAfterRerender)</mark></em> returns true.</p><p>React doesn't perform the deep comparison of objects. If the result of this comparison is true, then React will leave that component in peace and move on to the next one.</p><p>When elements are passed as props to a component, and this component triggers a re-render through a state update, elements that are passed as props won't re-render.</p>
10
New cards

Passing Components as props differences

"children" are just props and behave like any other prop when they are passed via JSX nesting syntax.

And it will have exactly the same performance benefits as passing Elements as props as well! Whatever is passed through props won't be affected by the state change of the component that receives those props.

<p>"children" are just props and behave like any other prop when they are passed via JSX nesting syntax.</p><p>And it will have exactly the same performance benefits as passing Elements as props as well! Whatever is passed through props won't be affected by the state change of the component that receives those props.</p>
11
New cards

Conditional rendering and performance

When you declare an element, such as <Footer />, it creates an object in memory, not an actual rendered component.

This object is only rendered when it is included in the return statement of a component, like ModalDialog. Therefore, even if the dialog is not open, the Footer element is not rendered, and there are no performance implications.

This principle also applies to routing patterns in React, where elements are only rendered when their corresponding route matches the URL.

<p>When you declare an element, such as <code>&lt;Footer /&gt;</code>, it creates an object in memory, not an actual rendered component. </p><p>This object is only rendered when it is included in the return statement of a component, like <code>ModalDialog</code>. Therefore, even if the dialog is not open, the <code>Footer</code> element is not rendered, and there are no performance implications. </p><p>This principle also applies to routing patterns in React, where elements are only rendered when their corresponding route matches the URL.</p>
12
New cards

Default values for the elements as from props

In some cases, we don’t want to pass everything in a component as props. Therefore, we need to have some limitations as default values.

The solution involves using React.cloneElement to clone the (For example) Icon and merge default props with the original icon's props.

This ensures that the Button can control the icon's appearance while still allowing consumers to override defaults if needed.

This approach simplifies usage and ensures consistency across different button states.

This pattern, however, is very fragile. It's too easy to make a mistake there, so use it only for very simple cases.

<p>In some cases, we don’t want to pass everything in a component as props. Therefore, we need to have some limitations as default values.</p><p>The solution involves using <code>React.cloneElement</code> to clone the (For example) Icon and merge default props with the original icon's props. </p><p>This ensures that the <code>Button</code> can control the icon's appearance while still allowing consumers to override defaults if needed. </p><p>This approach simplifies usage and ensures consistency across different button states.</p><p>This pattern, however, is very fragile. It's too easy to make a mistake there, so use it only for very simple cases.</p>
13
New cards
14
New cards
15
New cards

A render prop (or render function)

In React, instead of passing elements as props, you can use a render prop (or render function).

A render prop is a function that returns an element, similar to a component but called directly by you.

For example, in a Button component, you can pass a render function for an icon. This function is called where the icon should be rendered. This approach allows you to customize the icon's properties without cloning elements. You can pass default props to the render function and spread them over the icon, making it flexible and adaptable to different needs.

(57-59)

<p>In React, instead of passing elements as props, you can use a render prop (or render function).</p><p>A render prop is a function that returns an element, similar to a component but called directly by you.</p><p>For example, in a <code>Button</code> component, you can pass a render function for an icon. This function is called where the icon should be rendered. This approach allows you to customize the icon's properties without cloning elements. You can pass default props to the render function and spread them over the icon, making it flexible and adaptable to different needs.</p><p>(57-59)</p>
16
New cards

Sharing stateful logic: children as render props

Render props can be used to share stateful logic between components. This often goes hand-in-hand with the "children as props" pattern.

(61-69)

<p>Render props can be used to share stateful logic between components. This often goes hand-in-hand with the "children as props" pattern.</p><p>(61-69)</p>
17
New cards

Hooks replaced render props

So why learn this pattern at all? A few reasons for this:

  • Render props for configuration and flexibility use cases, described at the beginning, are still very viable.

  • If you are working on a project that is a few years old, this pattern will be all over the codebase. It was really popular before the introduction of hooks, especially for encapsulating form validation logic. A few popular libraries still use it to this day.

  • It can still be useful for specific scenarios, such as when the logic and state that you want to share depend on a DOM element.


<p>So why learn this pattern at all? A few reasons for this: </p><ul><li><p>Render props for configuration and flexibility use cases, described at the beginning, are still very viable. </p></li><li><p>If you are working on a project that is a few years old, this pattern will be all over the codebase. It was really popular before the introduction of hooks, especially for encapsulating form validation logic. A few popular libraries still use it to this day. </p></li><li><p>It can still be useful for specific scenarios, such as when the logic and state that you want to share depend on a DOM element.</p></li></ul><p></p>
18
New cards

useEffect re-rendering

When the dependencies change in the useEffect hook, it doesn't re-render the component itself. Instead, it re-runs the effect function inside the useEffect.

  • Component Re-render: When a component re-renders, it means React is calling the component function again to update the UI based on the new state or props.

  • useEffect Re-run: If the dependencies specified in the useEffect hook change between renders, React will re-run the effect function. This means the code inside the useEffect hook will execute again.

So React will compare submit before and after re-render in order to determine whether it should run the useEffect hook this time. The comparison will always return false since it's a new reference each time. As a result, the useEffect hook will be triggered on every re render


<p>When the dependencies change in the <code>useEffect</code> hook, it doesn't re-render the component itself. Instead, it re-runs the effect function inside the <code>useEffect.</code></p><ul><li><p><strong>Component Re-render</strong>: When a component re-renders, it means React is calling the component function again to update the UI based on the new state or props.</p></li><li><p><strong>useEffect Re-run</strong>: If the dependencies specified in the <code>useEffect</code> hook change between renders, React will re-run the effect function. This means the code inside the <code>useEffect</code> hook will execute again.</p></li></ul><p>So React will compare submit before and after re-render in order to determine whether it should run the useEffect hook this time. The comparison will always return false since it's a new reference each time. As a result, the useEffect hook will be triggered on every re render</p><p></p>
19
New cards

Major use cases where we actually need to memoize props on a component.

  1. Child Components: If you pass props down to child components and those props are derived from parent state or props, memoizing can help prevent unnecessary re-renders of the child components when the parent re-renders.

  2. When a component is wrapped in React.memo, it means that React will memoize the component. This means that React will only re-render the component if its props change. If the props remain the same, React will skip the re-rendering process and use the previously rendered output.


<ol><li><p><strong>Child Components</strong>: If you pass props down to child components and those props are derived from parent state or props, memoizing can help prevent unnecessary re-renders of the child components when the parent re-renders.</p></li><li><p><strong>When a component is wrapped in </strong><code>React.memo</code><strong>,</strong> it means that React will memoize the component. This means that React will only re-render the component if its props change. If the props remain the same, React will skip the re-rendering process and use the previously rendered output.</p></li></ol><p></p>
20
New cards

What is React.memo

React.memo (or just memo) is a utility provided by React that allows you to memoize a component. When a component is wrapped in React.memo, React will only re-render the component if its props change. If the props remain unchanged, React will skip the re-rendering process, preventing unnecessary re-renders.

This is particularly useful when a component's re-render is triggered by its parent. If any of the props change, the component wrapped in React.memo will re-render as usual.

<p><code>React.memo</code> (or just <code>memo</code>) is a utility provided by React that allows you to memoize a component. When a component is wrapped in <code>React.memo</code>, React will only re-render the component if its props change. If the props remain unchanged, React will skip the re-rendering process, preventing unnecessary re-renders. </p><p>This is particularly useful when a component's re-render is triggered by its parent. If any of the props change, the component wrapped in <code>React.memo</code> will re-render as usual.</p>
21
New cards

Using React.memo function on components has followe certain rules.

  1. never spread props that are coming from other components.

  2. avoid passing non-primitive props that are coming from other components. Even the explicit example like the one above is still quite fragile. If any of those props are non-memoized objects or functions, memoization will break again.

  3. avoid passing non-primitive values that are coming from custom hooks.

(80-83)


<ol><li><p>never spread props that are coming from other components.</p></li><li><p>avoid passing non-primitive props that are coming from other components. Even the explicit example like the one above is still quite fragile. If any of those props are non-memoized objects or functions, memoization will break again.</p></li><li><p>avoid passing non-primitive values that are coming from custom hooks.</p></li></ol><p>(80-83)</p><p></p>
22
New cards

React.memo and children

  • Children Prop: When you pass children to a component using JSX, it creates a new object for the children prop on every render. For example, <ChildMemo><div>Some text here</div></ChildMemo> creates a new object for the children prop each time the component re-renders.

  • Memoization Check: React.memo performs a shallow comparison of the props to determine if the component should re-render. Since the children prop is a new object on every render, the comparison will always return false, causing the component to re-render even if the content hasn't changed.


<ul><li><p><strong>Children Prop</strong>: When you pass children to a component using JSX, it creates a new object for the children prop on every render. For example, <code>&lt;ChildMemo&gt;&lt;div&gt;Some text here&lt;/div&gt;&lt;/ChildMemo&gt;</code> creates a new object for the <code>children</code> prop each time the component re-renders.</p></li><li><p><strong>Memoization Check</strong>: <code>React.memo</code> performs a shallow comparison of the props to determine if the component should re-render. Since the <code>children</code> prop is a new object on every render, the comparison will always return <code>false</code>, causing the component to re-render even if the content hasn't changed.</p></li></ul><p></p>
23
New cards

React.memo and memoized children

  • Passing a memoized component in a memoize component as a children prop will break the memoization.

  • We need to memoize the child component inside the memoized parent component


<ul><li><p>Passing a memoized component in a memoize component as a children prop will break the memoization.</p></li><li><p>We need to memoize the child component inside the memoized parent component</p></li></ul><p></p>
24
New cards

What is an expensive calculation

No one knows what’s an expensive calcualtion until it’s measured:

  • On a device that is representative of your user base

  • In context

  • In comparison with the rest of the stuff that is happening at the same time

  • In comparison with how it was before or the ideal state

It always depends. "Measure first" should be the default thinking when there is an urge to wrap something in useMemo because it's an "expensive calculation."

remember to measure how long it takes to re-render component elements as well; if you wrap every JavaScript calculation in useMemo and gain 10ms, but re-rendering components still takes almost 200ms, it complicates the code without any visible gain.

25
New cards

React components rendering and Raw JS

The performance benefits of using useMemo in React. It highlights that calculations performed within useMemo are generally much faster than re-rendering actual components.

For example, sorting an array of 300 items takes less than 2 milliseconds, while re-rendering list elements from that array can take more than 20 milliseconds.

26
New cards

Diffing & Reconciliation

This Virtual DOM is just a giant object with all the components that are supposed to render, all their props, and their children - which are also objects of the same shape. Just a tree.

  • DOM elements like input or label will have their "type" as strings, and React will know to convert them to the DOM elements directly.

  • But if we're rendering React components, It just grabs the entire component’s function that we know as the Input component and puts it there in the “type” property of the returned object.


And then, when React gets a command to mount the app (initial render), it iterates over that tree and does the following:

  • If the "type" is a string, it generates the HTML element of that type.

  • If the "type" is a function (i.e., our component), it calls it and iterates over the tree that this function returned.


<p>This Virtual DOM is just a giant object with all the components that are supposed to render, all their props, and their children - which are also objects of the same shape. Just a tree.</p><ul><li><p>DOM elements like input or label will have their "type" as strings, and React will know to convert them to the DOM elements directly.</p></li><li><p>But if we're rendering React components, It just grabs the entire component’s function that we know as the Input component and puts it there in the “type” property of the returned object.</p></li></ul><p></p><p>And then, when React gets a command to mount the app (initial render), it iterates over that tree and does the following: </p><ul><li><p>If the "type" is a string, it generates the HTML element of that type. </p></li><li><p>If the "type" is a function (i.e., our component), it calls it and iterates over the tree that this function returned.</p></li></ul><p></p>
27
New cards

Reconciliation when A component’s state is updated

When a state update in a component is triggered, React needs to update all the elements on the screen with the new data that comes from that state update. Or maybe add or remove some new elements. React starts from where the state update was initiated.

  • Ssame Type: If the "type" field of the component before and after the state update is the same, React marks the component as needing an update and triggers a re-render. This means that React will update the existing component with the new state or props without unmounting it.

  • Different Type: If the "type" field of the component before and after the state update is different, React unmounts the previous component and mounts the new one. This means that React will remove the old component from the DOM and add the new component, effectively replacing it.


<p>When a state update in a component is triggered, React needs to update all the elements on the screen with the new data that comes from that state update. Or maybe add or remove some new elements. React starts from where the state update was initiated.</p><ul><li><p><strong>Ssame Type</strong>: If the "type" field of the component before and after the state update is the same, React marks the component as needing an update and triggers a re-render. This means that React will update the existing component with the new state or props without unmounting it.</p></li><li><p><strong>Different Type</strong>: If the "type" field of the component before and after the state update is different, React unmounts the previous component and mounts the new one. This means that React will remove the old component from the DOM and add the new component, effectively replacing it.</p></li></ul><p></p>
28
New cards

Reconciliation and arrays

when React sees an array of children instead of an individual item, it just iterates over it and then compares "before" and "after" elements and their "type" according to their position in the array.

Even if some of those elements are rendered conditionally, React will still have a stable number of items in that array. Just sometimes, those items will be null.

And when React starts comparing them, item by item, it will be:

  • the first item, Checkbox before and after → re-render Checkbox

  • the second item, null before and Input after → mount Input

  • third item, Input before, null after → unmount Input

(105-108)

29
New cards

Reconciliation and “Key“ prop

React handles dynamic lists of components. When the order of components in an array changes, React needs a way to identify which component is which to ensure the correct elements are re-used.

This is achieved using a "key" property, which acts as a unique identifier for each element. By using keys, React can maintain the state and DOM of each component even when their order changes in the array. This prevents issues like state being incorrectly associated with the wrong component after re-ordering.

(108-110)

<p>React handles dynamic lists of components. When the order of components in an array changes, React needs a way to identify which component is which to ensure the correct elements are re-used. </p><p>This is achieved using a "key" property, which acts as a unique identifier for each element. By using keys, React can maintain the state and DOM of each component even when their order changes in the array. This prevents issues like state being incorrectly associated with the wrong component after re-ordering.</p><p>(108-110)</p>
30
New cards

"Key" attribute and memoized list

  • For static arrays (those that don't change their elements or positions), wrap the item element in React.memo and use a stable identifier (like an id or array index) as the key. (If the parent component re-renders, InputMemo components won't re-render because they are wrapped in React.memo and the key hasn't changed.)

  • Dynamic Arrays and Keys:

    • For dynamic arrays (those that can change their order or number of items), the key plays a crucial role.

    • Using the array index as a key can cause issues if the array is reordered, as React may re-render items unnecessarily.

(111-116)

<ul><li><p>For static arrays (those that don't change their elements or positions), wrap the item element in <code>React.memo</code> and use a stable identifier (like an <code>id</code> or array index) as the <code>key</code>. <em>(If the parent component re-renders, </em><code>InputMemo</code><em> components won't re-render because they are wrapped in </em><code>React.memo</code><em> and the </em><code>key</code><em> hasn't changed.)</em></p></li><li><p><strong>Dynamic Arrays and Keys</strong>:</p><ul><li><p>For dynamic arrays (those that can change their order or number of items), the <code>key</code> plays a crucial role.</p></li><li><p>Using the array index as a <code>key</code> can cause issues if the array is reordered, as React may re-render items unnecessarily.</p></li></ul></li></ul><p>(111-116)</p>
31
New cards

State reset technique

  • "key" is just an attribute of an element; it's not limited to dynamic arrays. In any children's array, it will behave exactly the same way.

  • React treats components children as the same between re-renders. To fix this, we need to make React realize that the Input components are different between re-renders by adding a Key to the elements

  • Be careful here, though. It's not just "state reset" as you can see. It forces React to unmount a component completely and mount a new one from scratch. For big components, that might cause performance problems. The fact that the state is reset is just a by-product of this total destruction.


<ul><li><p>"key" is just an attribute of an element; it's not limited to dynamic arrays. In any children's array, it will behave exactly the same way.</p></li><li><p>React treats components children as the same between re-renders. To fix this, we need to make React realize that the <code>Input</code> components are different between re-renders by adding a Key to the elements</p></li><li><p>Be careful here, though. It's not just "state reset" as you can see. It forces React to unmount a component completely and mount a new one from scratch. For big components, that might cause performance problems. The fact that the state is reset is just a by-product of this total destruction.</p></li></ul><p></p>
32
New cards

Using “Key“ to force reuse of an existing element

  • The key attribute can also help reuse existing elements in React.

  • When rendering the Input element in different positions in the children array, adding the same key value to both inputs can make React reuse the element.

  • React sees an array of children and notices that before and after re-renders, there is an element with the Input type and the same key.

  • React reuses the already created instance for the Input component, preserving the state even though the Input components are technically different.


<ul><li><p>The <code>key</code> attribute can also help reuse existing elements in React.</p></li><li><p>When rendering the <code>Input</code> element in different positions in the children array, adding the same <code>key</code> value to both inputs can make React reuse the element.</p></li><li><p>React sees an array of children and notices that before and after re-renders, there is an element with the <code>Input</code> type and the same <code>key</code>.</p></li><li><p>React reuses the already created instance for the <code>Input</code> component, preserving the state even though the <code>Input</code> components are technically different.</p></li></ul><p></p>
33
New cards

Why we don’t need keys outside of arrays?

In a dynamic array. React doesn't know what you will do with this array during the next re-render: remove, add, or rearrange items, or maybe leave them as-is. So it forces you to add the "key" as a precautionary measure, in case you're messing with the array on the fly

<p>In a dynamic array. React doesn't know what you will do with this array during the next re-render: remove, add, or rearrange items, or maybe leave them as-is. So it forces you to add the "key" as a precautionary measure, in case you're messing with the array on the fly</p>
34
New cards
<p>Dynamic arrays and normal elements together</p>

Dynamic arrays and normal elements together

When we mix dynamic and static elements, like in the code above, React simply creates an array of those dynamic elements and makes that entire array the very first child in the children's array. This is going to be the definition object for that code:

<p>When we mix dynamic and static elements, like in the code above, React simply creates an array of those dynamic elements and makes that entire array the very first child in the children's array. This is going to be the definition object for that code:</p>
35
New cards

What is a High-Order Component

it's a function that accepts a component as one of its arguments, executes some logic, and then returns another component that renders the component from the argument.

(hooks replaced this as well.)

<p>it's a function that accepts a component as one of its arguments, executes some logic, and then returns another component that renders the component from the argument. </p><p><em>(hooks replaced this as well.)</em></p>
36
New cards

Use cases of High-Order Components

while hooks have probably replaced 99% of shared logic concerns and 100% of use cases for accessing context, higher-order components can still be useful even in modern code. Mostly for enhancing callbacks, React lifecycle events, and intercepting DOM and keyboard events. Only if you're feeling fancy, of course. Those use cases can also be implemented with hooks, just not as elegantly.

37
New cards

Enhancing callbacks (A HOC use case)

The problem we're trying to solve is how to consistently send advanced logging events whenever a callback, like an onClick event, is triggered across various components in a React application.

Using hooks, you might add logging directly within each component's callback, but this approach can lead to repetitive code and potential errors. For example, you might have a Button component with an onClick callback that logs events, and then you would need to repeat this logic in every component that requires logging.

<p>The problem we're trying to solve is how to consistently send advanced logging events whenever a callback, like an <code>onClick</code> event, is triggered across various components in a React application.</p><p>Using hooks, you might add logging directly within each component's callback, but this approach can lead to repetitive code and potential errors. For example, you might have a <code>Button</code> component with an <code>onClick</code> callback that logs events, and then you would need to repeat this logic in every component that requires logging.</p>
38
New cards

Adding data to the higher-order component

Considering that a higher-order component is nothing more than just a function, we can do that easily. We just need to add some other arguments to the function, that's it.

<p>Considering that a higher-order component is nothing more than just a function, we can do that easily. We just need to add some other arguments to the function, that's it.</p>
39
New cards

Adding dynamic data to HOC as a prop

What if we want to send different texts in different contexts of where the button is used?

Also very easy to solve: instead of passing that text as a function's argument, we can just pass it as a prop to the resulting button.

<p>What if we want to send different texts in different contexts of where the button is used?</p><p>Also very easy to solve: instead of passing that text as a function's argument, we can just pass it as a prop to the resulting button. </p>
40
New cards

Enhancing React lifecycle events

We are not limited to clicks and callbacks with HOC.

Remember, these are just components, we can do whatever we want and need. We can use everything React has to offer.

For example, we can send those logging events when a component is mounted.

<p>We are not limited to clicks and callbacks with HOC. </p><p>Remember, these are just components, we can do whatever we want and need. We can use everything React has to offer.</p><p>For example, we can send those logging events when a component is mounted.</p>
41
New cards

Intercepting DOM events

Another very useful application of higher-order components is intercepting various DOM and keyboard events.

Imagine, for example, you're implementing some sort of keyboard shortcuts functionality for your page. When specific keys are pressed, you want to do various things, like opening dialogs, creating issues, etc.

Do it manually with useEffect will lead to multiple duplicates of logic in various components. But the same story as with onClick logging - what if you have multiple components where you want to see this logic? Copy-paste that event.stopPropagation everywhere?

<p><strong>Another very useful application of higher-order components is intercepting various DOM and keyboard events. </strong></p><p>Imagine, for example, you're implementing some sort of keyboard shortcuts functionality for your page. When specific keys are pressed, you want to do various things, like opening dialogs, creating issues, etc.</p><p>Do it manually with useEffect will lead to multiple duplicates of logic in various components. But the same story as with onClick logging - what if you have multiple components where you want to see this logic? Copy-paste that event.stopPropagation everywhere?</p>
42
New cards

Context (understanding the concept)

Context (or any context-like state management library) is very powerful. What they allow us to do is escape that tree of components and, instead of passing data through props, we can pass it directly from the component at the very top to the component at the bottom

<p>Context (or any context-like state management library) is very powerful. <strong>What they allow us to do is escape that tree of components and, instead of passing data through props,</strong> we can pass it directly from the component at the very top to the component at the bottom</p>
43
New cards

createContext API

  • createContext lets you create a context that components can provide or read.

  • createContext returns a context object.


<ul><li><p><code>createContext</code><span> lets you create a </span>context<span> that components can provide or read.</span></p></li><li><p><code>createContext</code><span> returns a context object.</span></p></li></ul><p></p>
44
New cards

useContext Hook

Now every component that happens to be rendered down the tree from that provider of the created Context (even if they are passed as props like our now have access to that children !) will value through the useContext hook.

<p>Now every component that happens to be rendered down the tree from that provider of the created Context (even if they are passed as props like our now have access to that children !) will value through the useContext hook.</p>
45
New cards

Context concerns

There are three major things that you need to know like the back of your hand when introducing Context into the app:

  • Context consumers will re-render when the value on the Provider changes.

  • All of them will re-render, even if they don't use the part of the value that actually changed.

  • Those re-renders can't be prevented with memoization (easily).


46
New cards

Context value change

Every time the value prop on the Context provider changes, everyone who uses this Context will re-render.

If the parent component of the component that wraps Context Provider, state change, then the Value props changes, and causing all the components that use Context will render

<p>Every time the value prop on the Context provider changes, everyone who uses this Context will re-render.</p><p>If the parent component of the component that wraps Context Provider, state change, then the <strong>Value</strong> props changes, and causing all the components that use Context will render </p>
47
New cards

Context value change re-rendering solution

To solve re-rendering problem Value prop that causes components to re-render that use Context, is using useMemo and useCallback to memoize the value passed to the provider.

This is one of the few cases where always memoizing by default is actually not premature optimization. It will prevent much bigger problems in the future that will almost inevitably occur.

<p>To solve re-rendering problem <strong>Value</strong> prop that causes components to re-render that use Context, is using <strong>useMemo</strong> and <strong>useCallback </strong>to memoize the value passed to the provider.</p><p>This is one of the few cases where always memoizing by default is actually not premature optimization. It will prevent much bigger problems in the future that will almost inevitably occur.</p>
48
New cards

Preventing unnecessary Context re-use an interesting technique called "splitting providers" to achieve the desired result.renders: split providers

When using React Context, all components that consume the context will re-render whenever the context value changes, even if the part of the context they use hasn't changed (components will re-render even if the specific data it relies on within the context hasn't been updated.). This can lead to unnecessary re-renders.

To avoid unnecessary re-renders, you can split the context into multiple contexts, each responsible for a specific part of the state

<p>When using React Context, all components that consume the context will re-render whenever the context value changes, even if the part of the context they use hasn't changed <em>(components will re-render even if the specific data it relies on within the context hasn't been updated.).</em> This can lead to unnecessary re-renders. </p><p>To avoid unnecessary re-renders, you can split the context into multiple contexts, each responsible for a specific part of the state</p>
49
New cards

Split providers. To prevent unnecessary context rerendering

We can use an interesting technique called "splitting providers" to achieve the desired result.

Instead of just one Context that holds everything, we can create two: one will hold the value that changes, and another one will hold those that don't.

(154-158)

<p>We can use an interesting technique called "splitting providers" to achieve the desired result.</p><p>Instead of just one Context that holds everything, we can create two: one will hold the value that changes, and another one will hold those that don't.</p><p>(154-158)</p>
50
New cards
<p>Reducers</p>

Reducers

useReducer is a different way to manage a component's state.

Instead of being aware of the state we're changing and manipulating it manually, the reducers pattern allows us just to dispatch named "actions".

This reducer pattern is especially powerful when you have multiple state variables and more complex actions to perform on the state, rather than just flipping a boolean from false to true.

But from the re-render perspective, it's the same as useState : updating the state through dispatch will force the component to re-render.

<p><strong>useReduce</strong>r is a different way to manage a component's state. </p><p>Instead of being aware of the state we're changing and manipulating it manually,<strong> the reducers pattern allows us just to dispatch named "actions". </strong></p><p>This reducer pattern is especially powerful when <strong>you have multiple state variables and more complex actions to perform on the state</strong>, <strong>rather than just flipping a boolean from false to true.</strong> </p><p><mark data-color="yellow" style="background-color: yellow; color: inherit">But from the re-render perspective, it's the same as useState : updating the state through dispatch will force the component to re-render.</mark></p>
51
New cards

Context selectors

Context selectors are a way to optimize the use of React Context in performance-sensitive areas by selecting only the necessary values from the context, thus avoiding unnecessary re-renders. When you use useContext, any change in the context value will trigger a re-render of every consumer, which can be inefficient.

To solve this issue, is to use Higher Order Components (HOCs) to create a custom hook that only subscribes to the specific part of the context you need.


when the Context value changes,

  • the component that uses anything from Context will still re-render: our unnamed component that we return from the withNavigationOpen function.

  • But this component renders another component that is memoized. So if its props don't change, it won't re-render because of this re-render.

  • And the props won't change: those that are spread are coming from "outside", so they won't be affected by the context change.

  • And the open function is memoized inside the Context provider itself.


<p>Context selectors are a way to optimize the use of React Context in performance-sensitive areas <strong>by selecting only the necessary values from the context,</strong> thus avoiding unnecessary re-renders. When you use <code>useContext</code>, any change in the context value will trigger a re-render of every consumer, which can be inefficient.</p><p>To solve this issue, i<strong>s to use Higher Order Components (HOCs) to create a custom hook that only subscribes to the specific part of the context you need.</strong> </p><p></p><p>when the Context value changes, </p><ul><li><p>the component that uses anything from Context will still re-render: our unnamed component that we return from the <em>withNavigationOpen </em>function. </p></li><li><p>But this component renders another component that is memoized. So if its props don't change, it won't re-render because of this re-render. </p></li><li><p>And the props won't change: those that are spread are coming from "outside", so they won't be affected by the context change. </p></li><li><p>And the open function is memoized inside the Context provider itself.</p></li></ul><p></p>
52
New cards
53
New cards
54
New cards
55
New cards

Ref update doesn’t trigger re-render

One of the biggest and most visible differences between Refs and state is that Refs update don't cause re-renders.

If you put both of those forms, you'll see that the console.log in Form component with the state re-renders with every keystroke, while the Form with the Ref stays quiet

Change in the value of a Ref will not be picked up by the downstream cocmponents if passed as props as a primitive value either

<p>One of the biggest and most visible differences between Refs and state is that Refs update don't cause re-renders.</p><p>If you put both of those forms, you'll see that the console.log in Form component with the state re-renders with every keystroke, while the Form with the Ref stays quiet</p><p>Change in the value of a Ref will not be picked up by the downstream cocmponents if passed as props as a primitive value either</p>
56
New cards

Ref update is synchronous and mutable

  • Ref updates are synchronous. We are just mutating an object after all, which is a synchronous operation in JavaScript.

  • State, however, is typically asynchronous. It's even more than asynchronous: state updates are run in "snapshots". React has a complicated system that manages it and makes sure that the data and components within one "snapshot" are consistent and updated properly.

  • Ref, however, has none of that: we're modifying an object directly, and that's all there is to it.


<ul><li><p>Ref updates are synchronous. We are just mutating an object after all, which is a synchronous operation in JavaScript.</p></li><li><p><strong>State, however, is typically asynchronous. It's even more than asynchronous: state updates are run in "snapshots".</strong> React has a complicated system that manages it and makes sure that the data and components within one "snapshot" are consistent and updated properly.</p></li><li><p>Ref, however, has none of that: we're modifying an object directly, and that's all there is to it.</p></li></ul><p></p>
57
New cards

When can we use Ref

So, considering these differences, when is it actually okay to use Ref to store something, and when is state preferred? Ask yourself these questions:

  • Is this value used for rendering components?

  • Is this value passed as props to other components in any way?

If the answer to both of these questions is "nope", then Ref is okay to use

<p>So, considering these differences, when is it actually okay to use Ref to store something, and when is state preferred? Ask yourself these questions:</p><ul><li><p>Is this value used for rendering components?</p></li><li><p>Is this value passed as props to other components in any way?</p></li></ul><p>If the answer to both of these questions is "nope", then Ref is okay to use</p>
58
New cards

Assigning DOM elements to Ref

The important thing to remember here is that Ref will be assigned only after the element is rendered by React and its associated DOM element is created.

We need something to assign to that Ref, isn't it? That means that the ref.current value won't be available right away, and logic like this will just not work: (Check img) Therefore, we should not

We should only read and write ref.current either in the useEffect hook or in callbacks.

<p>The important thing to remember here is that <em>Ref </em>will be assigned only after the element is rendered by React and its associated DOM element is created.</p><p>We need something to assign to that Ref, isn't it? That means that the ref.current value won't be available right away, and logic like this will just not work: (Check img) Therefore, we should not</p><p>We should only read and write ref.current either in the useEffect hook or in callbacks.</p>
59
New cards

Passing Ref from parent to child as a prop

Ref Updates: Remember that updating the ref's current value doesn't trigger a re-render. This can be useful for performance optimization but requires careful handling to ensure the UI stays in sync.

<p><strong>Ref Updates</strong>: Remember that updating the ref's current value doesn't trigger a re-render. This can be useful for performance optimization but requires careful handling to ensure the UI stays in sync.</p>
60
New cards

Passing Ref from parent to child with forwardRef (forwardRef is not needed in react 19)

  • Passing a Ref Prop Directly:

    • When you pass a ref as a regular prop, it means you're treating it just like any other prop. The child component gets this prop and can use it as needed, but the parent component doesn't directly control the ref.

    • When you pass a ref prop directly to a child component, it doesn't automatically become a ref to a DOM element or a class component instance. Instead, it is treated as a regular prop.

    • This means you need to manually handle the ref in the child component, which can lead to confusion and additional boilerplate code.

  • Using forwardRef:

    • When you use forwardRef, you're allowing the parent component to directly control the ref of the child component. This is more powerful and aligns with React's best practices.

    • When you use forwardRef, the ref is automatically forwarded to the child component, making it easier to reference DOM elements or class component instances.


<ul><li><p><strong>Passing a Ref Prop Directly</strong>:</p><ul><li><p>When you pass a ref as a regular prop, it means you're treating it just like any other prop. The child component gets this prop and can use it as needed, but the parent component doesn't directly control the ref.</p></li><li><p>When you pass a ref prop directly to a child component, it doesn't automatically become a ref to a DOM element or a class component instance. Instead, it is treated as a regular prop.</p></li><li><p>This means you need to manually handle the ref in the child component, which can lead to confusion and additional boilerplate code.</p></li></ul></li><li><p><strong>Using </strong><code>forwardRef</code>:</p><ul><li><p>When you use <code>forwardRef</code>, you're allowing the parent component to directly control the ref of the child component. This is more powerful and aligns with React's best practices.</p></li><li><p>When you use <code>forwardRef</code>, the ref is automatically forwarded to the child component, making it easier to reference DOM elements or class component instances.</p></li></ul></li></ul><p></p>
61
New cards

What is useImperativeHandle Hook?

useImperativeHandle is a React hook that allows you to customize the value that is assigned to a ref in a parent component. This hook is particularly useful when you need to expose a specific set of methods or properties from a child component to a parent component, giving you more control over the way your components communicate.

Here's a high-level overview of how useImperativeHandle works:

  1. Define the Custom Handle: Within your functional component, you use useImperativeHandle to specify which methods or properties should be available to the parent component.

  2. Use forwardRef: Since functional components don't naturally handle refs, you need to wrap your component with forwardRef to properly pass the ref from the parent.

Just remember: the imperative way to trigger something is more of an escape hatch in React. In 99% of cases, the normal props/callbacks data flow is more than enough.

<p><code>useImperativeHandle</code> is a React hook that allows you to customize the value that is assigned to a ref in a parent component. This hook is particularly useful when you need to expose a specific set of methods or properties from a child component to a parent component, giving you more control over the way your components communicate.</p><p>Here's a high-level overview of how <code>useImperativeHandle</code> works:</p><ol><li><p><strong>Define the Custom Handle</strong>: Within your functional component, you use <code>useImperativeHandle</code> to specify which methods or properties should be available to the parent component.</p></li><li><p><strong>Use </strong><code>forwardRef</code>: Since functional components don't naturally handle refs, you need to wrap your component with <code>forwardRef</code> to properly pass the ref from the parent.</p></li></ol><p>Just remember: the imperative way to trigger something is more of an escape hatch in React. In 99% of cases, the normal props/callbacks data flow is more than enough.</p>
62
New cards

Closures in React

a stale closure happens when a function captures the variables from its outer scope (such as state or props) at the time of its creation, and those variables become outdated due to subsequent re-renders or state updates.

The Issue in Detail

Let's recap the issue step by step:

  1. You have a form with an input field and a heavy component that you want to optimize using React.memo.

  2. The onClick handler submits the form data and needs to log the current value of the input field.

  3. The onClick handler is memoized using useCallback, but because it depends on the value state, it gets recreated on each state update.

The Dilemma

  • Memoizing onClick with useCallback and adding value as a dependency causes the onClick function to change every time the input value changes.

  • This results in React.memo not being effective, as the heavy component re-renders each time the onClick function changes.


<p>a stale closure happens when a function captures the variables from its outer scope (such as state or props) at the time of its creation, and those variables become outdated due to subsequent re-renders or state updates.</p><p><strong>The Issue in Detail</strong></p><p>Let's recap the issue step by step:</p><ol><li><p>You have a form with an input field and a heavy component that you want to optimize using <code>React.memo</code>.</p></li><li><p>The <code>onClick</code> handler submits the form data and needs to log the current value of the input field.</p></li><li><p>The <code>onClick</code> handler is memoized using <code>useCallback</code>, but because it depends on the <code>value</code> state, it gets recreated on each state update.</p></li></ol><p><strong>The Dilemma</strong></p><ul><li><p>Memoizing <code>onClick</code> with <code>useCallback</code> and adding <code>value</code> as a dependency causes the <code>onClick</code> function to change every time the input value changes.</p></li><li><p>This results in <code>React.memo</code> not being effective, as the heavy component re-renders each time the <code>onClick</code> function changes.</p></li></ul><p></p>
63
New cards

Javascript and React, scope and closures

  • In JavaScript, closures are useful for encapsulation and data persistence.

  • In React, every function inside a component is a closure, capturing state and props at the moment of definition.

  • Be mindful of stale closures, where a function retains outdated state due to memoization or missing dependencies.


<ul><li><p>In <strong>JavaScript</strong>, closures are useful for encapsulation and data persistence.</p></li><li><p>In <strong>React</strong>, every function inside a component is a closure, capturing state and props at the moment of definition.</p></li><li><p>Be mindful of <strong>stale closures</strong>, where a function retains outdated state due to memoization or missing dependencies.</p></li></ul><p></p>
64
New cards

The stale closure problem

Closures in JavaScript retain references to variables from their outer scope at the moment they are created. If a closure is stored and reused without being updated, it may reference outdated (or "stale") data.

The problem with stale closure is not checking a certain value of a variable already exists or not but the function, object, or variable that we assigned in memory which cannot be itself compared if it already exists or not.

<p><mark data-color="green" style="background-color: green; color: inherit">Closures in JavaScript </mark><strong><mark data-color="green" style="background-color: green; color: inherit">retain references to variables</mark></strong><mark data-color="green" style="background-color: green; color: inherit"> from their outer scope at the moment they are created. If a closure is stored and reused </mark><strong><mark data-color="green" style="background-color: green; color: inherit">without being updated</mark></strong><mark data-color="green" style="background-color: green; color: inherit">, it may reference outdated (or "stale") data.</mark></p><p>The problem with stale closure is not checking a certain value of a variable already exists or not but the function, object, or variable that we assigned in memory which cannot be itself compared if it already exists or not.</p>
65
New cards

Stale closures in React: useCallback

If you remember the Memoization with useMemo, useCallback, and React.memo chapter, the code in the previous flashcard should look familiar. And indeed, we just implemented exactly what the useCallback hook does for us. Every time we use useCallback , we create a closure, and the function that we pass to it is cached.

If we need access to state or props inside this function, we need to add them to the dependencies array in useCallback

This dependencies array in (useCallback, useMemo, memo, useFunction) is what makes React refresh that cached closure, exactly as we did when we compared value !== prevValue . If I forget about that array, our closure becomes stale:

<p>If you remember the Memoization with useMemo, useCallback, and React.memo chapter, the code in the previous flashcard should look familiar. And indeed, we just implemented exactly what the useCallback hook does for us. Every time we use useCallback , we create a closure, and the function that we pass to it is cached.</p><p>If we need access to state or props inside this function, we need to add them to the dependencies array in <em>useCallback</em></p><p>This dependencies array in <em>(useCallback, useMemo, memo, useFunction)</em> is what makes React refresh that cached closure, exactly as we did when we compared value !== prevValue . If I forget about that array, our closure becomes stale:</p>
66
New cards

Stale closures in React: Refs

The second most common way to introduce the stale closure problem, after useCallback and useMemo hooks, is Refs.

When using useRef to store a function, it maintains the initial closure when the component is mounted. This means that any state or props accessed within that function will always reflect their initial values unless the ref is updated manually.

<p>The second most common way to introduce the stale closure problem, after useCallback and useMemo hooks, is Refs.</p><p>When using <code>useRef</code> to store a function, it maintains the initial closure when the component is mounted. This means that any state or props accessed within that function will always reflect their initial values unless the <code>ref</code> is updated manually.</p>
67
New cards

Stale closures in React: React.memo

Every time we click on the button, we log "undefined". Our inside onClick is never updated.

When we create the onClick handler, it forms a closure with the initial state value ("undefined"). This closure is passed to the memoized component along with the title prop.

The comparison function only checks the title, which never changes, so HeavyComponent is never updated. As a result, it retains the reference to the initial onClick closure with the "undefined" value.

We can try many other things. But we don't have to do any heavy refactorings to escape that closures trap. There is one cool trick that can help us here.

(210-212)

<p>Every time we click on the button, we log "undefined". Our inside onClick is never updated.</p><p>When we create the <code>onClick</code> handler, <strong>it forms a closure with the initial state value ("undefined").</strong> This closure is passed to the memoized component along with the <code>title</code> prop. </p><p>The comparison function only checks the <code>title</code>, which never changes, so <code>HeavyComponent</code> is never updated. As a result, it retains the reference to the initial <code>onClick</code> closure with the "undefined" value.</p><p>We can try many other things. But we don't have to do any heavy refactorings to escape that closures trap. There is one cool trick that can help us here.</p><p>(210-212)</p>
68
New cards

Escaping the closure trap with Refs

useEffect without the dependency array will be triggered on every re-render.

Which is exactly what we want. So now in our ref.current we have a closure that is recreated with every re-render, so the state that is logged there is always the latest.

we can't just pass that ref.current to the memoized component. That value will differ with every re-render, so memoization just won't work. So instead, create a small empty function wrapped in useCallback with no dependencies for that. Then all we need to make it work is to call ref.current inside that memoized callback. ref is not in the dependencies of the doesn't need to be. useCallback ? It ref by itself never changes. It's just a reference to a mutable object that the useRef hook returns.

(212-217)

<p>useEffect without the dependency array will be triggered on every re-render.</p><p>Which is exactly what we want. So now in our ref.current we have a closure that is recreated with every re-render, so the state that is logged there is always the latest.</p><p>we can't just pass that ref.current to the memoized component. That value will differ with every re-render, so memoization just won't work. So instead, <strong>create a small empty function wrapped in useCallback with no dependencies for that. Then </strong>all we need to make it work is to <strong>call ref.current inside that memoized callback. </strong>ref is not in the dependencies of the doesn't need to be. useCallback ? It ref by itself never changes. It's just a reference to a mutable object that the useRef hook returns.</p><p>(212-217)</p>
69
New cards

Implementing advanced debouncing and throttling with Refs

One very important and quite common use case for Refs. It's storing various timers and timeout ids when dealing with functions like setInterval or debounce .

"Debouncing" and "Throttling" are techniques that allow us to skip function execution if that function is called too many times over a certain time period.

It's a very common scenario for various form elements. We usually would want to debounce/throttle inputs' onChange callbacks, for example, so that the form is not re-rendered with every keystroke

70
New cards

Debouncing

debounce is just a function that accepts a function, returns another function, and has a tracker inside that detects whether the passed function was called sooner than the provided interval. If sooner - then skip the execution and re-start the clock. If the interval has passed, call the passed function.

<p>debounce is just a function that accepts a function, returns another function, and has a tracker inside that detects whether the passed function was called sooner than the provided interval. If sooner - then skip the execution and re-start the clock. If the interval has passed, call the passed function.</p>
71
New cards

Throttle

Throttle is very similar, and the idea of keeping the internal tracker and a function that returns a function is the same.

If a user types something in the field, we want to send requests to the backend to save whatever they type "on the fly", without them pressing the "save" button explicitly. If a user is writing a poem in a field like that really, really fast, the "debounced" onChange callback will be triggered only once. And if something breaks while typing, the entire poem will be lost. The "throttled" callback will be triggered periodically, the poem will be regularly saved, and if a disaster occurs, only the last milliseconds of the poem will be lost. Much safer approach.

72
New cards

Debounced callback in React: dealing with re-renders (the solution to debouncing with useRef)

Debouncing in React can be tricky when working with state. Initially, a useCallback function is used to debounce input changes, but adding state dependencies causes the function to re-create on every render, making debounce ineffective.

To avoid this, useRef can store the debounced function, preventing unnecessary re-creation. However, since refs don’t update automatically, they require useEffect to update them with the latest state, or else the function becomes stale due to closures.

A more robust solution involves storing the function reference in a ref, updating it with useEffect, and using useMemo to create the debounced function only once. This allows the function to always access the latest state while avoiding unnecessary re-renders.

<p>Debouncing in React can be tricky when working with state. Initially, a <code>useCallback</code> function is used to debounce input changes, but adding state dependencies causes the function to re-create on every render, making debounce ineffective.</p><p>To avoid this, <code>useRef</code> can store the debounced function, preventing unnecessary re-creation. However, since refs don’t update automatically, they require <code>useEffect</code> to update them with the latest state, or else the function becomes stale due to closures.</p><p><strong>A more robust solution involves storing the function reference in a ref, updating it with </strong><code>useEffect</code><strong>, and using </strong><code>useMemo</code><strong> to create the debounced function only once. This allows the function to always access the latest state while avoiding unnecessary re-renders.</strong></p>
73
New cards

Browser rendering (Painting)

Browsers update the screen in frames rather than continuously, similar to presenting slides rather than drawing in real-time. They aim for 60 FPS (frames per second), meaning each frame updates approximately every 13ms—a process called "painting" in React.

Updates are managed as tasks in a queue. The browser picks and executes tasks until it runs out of time within the 13ms window, then refreshes the screen. This cycle repeats endlessly, enabling smooth experiences like scrolling and animations.

74
New cards

What is a Task in Painting

When it comes to normal JavaScript, it's everything that we put in the script tag and execute synchronously. (check img)

The main idea here is how the browser batches DOM updates and how synchronous tasks can block rendering.

  • DOM updates within a single task are processed together: Even though the border is updated three times (red → green → black), the browser only paints the final state (black). Intermediate states (red, green) are never shown.

  • Long-running tasks block rendering: If a task takes longer than 13ms (e.g., using waitSync(1000) to delay updates), the browser freezes, showing nothing until the task finishes.

  • This results in "blocking render" or "blocking painting", where the UI doesn’t update until the entire task completes.


<p>When it comes to normal JavaScript, it's everything that we put in the script tag and execute synchronously. (<em>check img)</em></p><p>The main idea here is <strong>how the browser batches DOM updates and how synchronous tasks can block rendering</strong>.</p><ul><li><p><strong>DOM updates within a single task are processed together</strong>: Even though the border is updated three times (<code>red → green → black</code>), the browser only <strong>paints the final state</strong> (<code>black</code>). Intermediate states (<code>red, green</code>) are never shown.</p></li><li><p><strong>Long-running tasks block rendering</strong>: If a task takes longer than <strong>13ms</strong> (e.g., using <code>waitSync(1000)</code> to delay updates), the browser <strong>freezes</strong>, showing nothing until the task finishes.</p></li><li><p>This results in <strong>"blocking render"</strong> or <strong>"blocking painting"</strong>, where the UI doesn’t update until the entire task completes.</p></li></ul><p></p>
75
New cards

What React does to Browser rendering (Browser Painting)

Now, although React is just JavaScript, it's not executed as one single task, of course. The internet would be unbearable if it was. The way to "break" a giant task like rendering an entire app into smaller ones is by using various "asynchronous" methods: callbacks, event handlers, promises, and so on.

React optimizes rendering by batching updates, prioritizing tasks, and rendering in chunks rather than blocking the browser.

76
New cards

useEffect vs useLayoutEffect

  • Execution Timing: useLayoutEffect runs synchronously after all DOM mutations but before the browser paints. But useEffect runs after the render is committed to the screen.

  • Use Case: It is used when you need to read layout and/or perform synchronous re-rendering before the browser updates the screen. This is useful for measurements or changing the DOM in a way that needs to be immediately visible.

  • Behavior: It blocks the browser from painting until the effect is complete, which caen potentially impact performance if overused.


<ul><li><p><strong>Execution Timing</strong>: <code>useLayoutEffect</code> runs synchronously after all DOM mutations but before the browser paints. But <code>useEffect</code> runs after the render is committed to the screen.</p></li><li><p><strong>Use Case</strong>: It is used when you need to read layout and/or perform synchronous re-rendering before the browser updates the screen. This is useful for measurements or changing the DOM in a way that needs to be immediately visible.</p></li><li><p><strong>Behavior</strong>: It blocks the browser from painting until the effect is complete, which caen potentially impact performance if overused.</p></li></ul><p></p>
77
New cards

How SSR works?

  1. when we have SSR enabled, the very first pass at rendering React components and calling all the lifecycle events is done on the server before the code reaches the browser. On the backend, some method calls something like React.renderToString(). React then goes through all the components in the app, "renders" them (i.e., just calls their functions), and produces the HTML that these components represent.

  2. Then, this HTML is injected into the page that is going to be sent to the browser.

  3. After that, the browser downloads the page, shows it to us, downloads all the scripts (including React), runs them (including React again), React goes through that pre-generated HTML, sprinkles some interactivity on it, and our page is now alive again.

  4. The problem here is: there is no browser yet (HTML generation happens on the server, not on the user's browser.) when we generate that initial HTML. Anything that would involve calculating actual sizes of elements (like we do in our useLayoutEffect ) will simply not work on the server. (because react is ran on the server). There are no elements with dimensions yet, just strings. And since the whole purpose of useLayoutEffect is to get access to the element's sizes, there is not much point in running it on the server. And React doesn't.

  5. As a result, what we see during the very first load when the browser shows us the page that is not interactive yet is what we rendered during the "first pass" stage in our component: the row of all the buttons, including the "more" button.


<ol><li><p>when we have SSR enabled,<mark data-color="green" style="background-color: green; color: inherit"> the very first pass at rendering React components and calling all the lifecycle events is done on the server before the code reaches the browser.</mark> On the backend, some method calls something like<em> React.renderToString().</em> React then goes through all the components in the app, "renders" them (i.e., just calls their functions), and produces the HTML that these components represent.</p></li><li><p>Then,<mark data-color="green" style="background-color: green; color: inherit"> this HTML is injected into the page that is going to be sent to the browser.</mark></p></li><li><p>After that, the browser downloads the page, shows it to us, downloads all the scripts (including React), runs them (including React again), React goes through that pre-generated HTML, sprinkles some interactivity on it, and our page is now alive again.</p></li><li><p>The problem here is: there is no browser yet <em>(HTML generation happens on the server, not on the user's browser.) </em>when we generate that initial HTML. Anything that would involve calculating actual sizes of elements (like we do in our useLayoutEffect ) will simply not work on the server. <em>(because react is ran on the server). </em>There are no elements with dimensions yet, just strings. And since the whole purpose of useLayoutEffect is to get access to the element's sizes, there is not much point in running it on the server. And React doesn't.</p></li><li><p>As a result, what we see during the very first load when the browser shows us the page that is not interactive yet is what we rendered during the "first pass" stage in our component: the row of all the buttons, including the "more" button.</p></li></ol><p></p>
78
New cards

How to fix some SSR problem that stop user interactivity in the browser?

How to fix it is a user experience problem and depends entirely on what you're willing to show to your users "by default."

We could show them some "loading" state instead of the menu. Or show one or two of the most important menu items. Or even hide the items completely and only render them on the client. It's up to you.

One way to do it is just to introduce a "shouldRender" state variable and flip it to "true" in useEffect. (check img), because useEffect will only run on the client, so the initial SSR pass will show us the substitute component. Then, the client code will kick in.

<p>How to fix it is a user experience problem and depends entirely on what you're willing to show to your users "by default."</p><p>We could show them some "loading" state instead of the menu. Or show one or two of the most important menu items. Or even hide the items completely and only render them on the client. It's up to you.</p><p>One way to do it is just to introduce a "shouldRender" state variable and flip it to "true" in useEffect. <em>(check img), because </em>useEffect will only run on the client, so the initial SSR pass will show us the substitute component. Then, the client code will kick in.</p>
79
New cards

Absolute is not that absolute (in CSS)

First of all, the absolute position is not exactly… absolute. It's actually relative: relative to the closest element with the position set to any value. In our case, it just works by accident: because I don't have any positioned elements between my modal dialog and the root of the app.

If the dialog happens to be rendered inside a div with relative (or sticky or position: absolute ) and this div is not in the middle of the page, then it all falls apart. The modal will be positioned in the middle of that div, not in the middle of the screen.

80
New cards

Understanding Stacking Context

Stacking Context is a concept in web design that helps determine the order in which elements are rendered on a web page along the Z-axis (the depth). Think of it like layers in a 3D space.

  • Elements are generally stacked according to their order in the DOM.

  • Example: If you have <div>grey</div>, <div>red</div>, and <div>green</div>, the green div will be in front of the red one, and the red one in front of the grey.

  • Elements with position: relative or position: absolute are always pushed forward.



<p><strong>Stacking Context</strong> is a concept in web design that helps determine the order in which elements are rendered on a web page along the Z-axis (the depth). Think of it like layers in a 3D space.</p><ul><li><p>Elements are generally stacked according to their order in the DOM.</p></li><li><p>Example: If you have <code>&lt;div&gt;grey&lt;/div&gt;</code>, <code>&lt;div&gt;red&lt;/div&gt;</code>, and <code>&lt;div&gt;green&lt;/div&gt;</code>, the green div will be in front of the red one, and the red one in front of the grey.</p></li><li><p>Elements with <code>position: relative</code> or <code>position: absolute</code> are always pushed forward.</p><p></p></li></ul><p></p>
81
New cards

Using Portals in Reacct

  • Portals allow you to easily render some elements, like modal dialogs, outside of their current DOM position so that the Stacking Context doesn't trap them.

  • When using Portals, the rules are:

    • What happens in React stays within the React hierarchy.

    • What happens outside of React follows DOM structure rules.


82
New cards

Types of data fetching

in the modern frontend world, we can loosely separate the concept of "data fetching" into two categories: initial data fetching and data fetching on demand.

  • Data on demand is something that you fetch after a user interacts with a page in order to update their experience. All the various autocompletes, dynamic forms, and search experiences fall under this category. In React, the fetch of this data is usually triggered in callbacks.

  • Initial data is the data you'd expect to see on a page right away when you open it. It's the data we need to fetch before a component ends up on the screen. It's something that we need to be able to show users some meaningful experience as soon as possible. In React, if no SSR is involved, fetching data like this usually happens in useEffect (or in componentDidMount for class components).

Interestingly enough, although these concepts seem totally different, the core principles and fundamental patterns of data fetching are exactly the same for both.


83
New cards

React Lifecycle and Data Fetching on render.

  • Lifecycle Importance: Plan fetch requests based on when the React component's lifecycle is triggered.

  • Conditional Rendering: Only trigger fetch requests when certain state changes occur.

  • Advanced Lifecycle Topics: Knowledge of triggers before/after painting, useLayoutEffect, and performance optimization becomes crucial in large apps.


<ul><li><p><strong>Lifecycle Importance</strong>: Plan fetch requests based on when the React component's lifecycle is triggered.</p></li><li><p><strong>Conditional Rendering</strong>: Only trigger fetch requests when certain state changes occur.</p></li><li><p><strong>Advanced Lifecycle Topics</strong>: Knowledge of triggers before/after painting, useLayoutEffect, and performance optimization becomes crucial in large apps.</p></li></ul><p></p>
84
New cards

Browser Limitations and Data Fetching

  • Complexity in Data Fetching: In large apps with many data fetching requests, firing all requests simultaneously and storing data in a global store can cause issues.

  • Browser Limitations: Browsers have a limit on the number of parallel requests to the same host (6 requests in Chrome for HTTP1). Exceeding this limit causes additional requests to queue.

  • Impact on Large Apps: For apps with numerous initial data fetching requests, this limitation can significantly slow down the user experience.


<ul><li><p><strong>Complexity in Data Fetching</strong>: In large apps with many data fetching requests, firing all requests simultaneously and storing data in a global store can cause issues.</p></li><li><p><strong>Browser Limitations</strong>: Browsers have a limit on the number of parallel requests to the same host (6 requests in Chrome for HTTP1). Exceeding this limit causes additional requests to queue.</p></li><li><p><strong>Impact on Large Apps</strong>: For apps with numerous initial data fetching requests, this limitation can significantly slow down the user experience.</p></li></ul><p></p>
85
New cards

Requests Waterfalls: How They Appear

The waterfall effect in data fetching occurs when multiple dependent requests are made sequentially, one after the other. Each subsequent request waits for the previous one to complete before starting. This can lead to significant delays in loading data and rendering components, especially in large applications with numerous data fetching operations.

Solution:

To mitigate the waterfall effect, consider optimizing your data fetching strategy:

  • Parallel Requests: Initiate multiple requests simultaneously rather than sequentially.

  • State Management: Use state management libraries to manage and share data across components efficiently.

  • Batch Requests: Combine multiple requests into a single batch request when possible.

(285-294)

<p>The waterfall effect in data fetching occurs when multiple dependent requests are made sequentially, one after the other. Each subsequent request waits for the previous one to complete before starting. This can lead to significant delays in loading data and rendering components, especially in large applications with numerous data fetching operations.</p><p><strong>Solution:</strong></p><p>To mitigate the waterfall effect, consider optimizing your data fetching strategy:</p><ul><li><p><strong>Parallel Requests</strong>: Initiate multiple requests simultaneously rather than sequentially.</p></li><li><p><strong>State Management</strong>: Use state management libraries to manage and share data across components efficiently.</p></li><li><p><strong>Batch Requests</strong>: Combine multiple requests into a single batch request when possible.</p></li></ul><p>(285-294)</p>
86
New cards

Race conditions

Race conditions occur due to asynchronous operations (Promises) and React's lifecycle. They can cause flickering content and data inconsistency when multiple fetch requests overlap.

87
New cards

Race condition reasons

Why Race Conditions Happen:

  1. Asynchronous Nature of Promises:

    • When you trigger a fetch request, it runs in the background while React continues rendering.

    • If multiple fetch requests are initiated (e.g., due to rapid state changes), they don't cancel each other out. Each request will complete independently, even if the data is no longer needed.

  2. React's Lifecycle:

    • When a component re-renders (e.g., due to a state or prop change), useEffect can trigger again.

    • If a fetch request is still pending from a previous render, and a new fetch is initiated, both requests will eventually complete and try to update the same state.

  3. Outdated State Updates:

    • If an earlier fetch completes after a later one, it will overwrite the state with outdated data.

    • This results in a "flash" of incorrect data or unexpected UI behavior.


88
New cards

Fixing race conditions: Force re-mounting

  1. Component Unmounting:

    • When navigating between pages, if the components are fully unmounted and remounted (e.g., using conditional rendering like {page === 'issue' && <Issue />}), the old component and its state are completely destroyed.

    • Any pending asynchronous operations (like fetch) in the unmounted component will not update the state because the component no longer exists.

  2. No Shared State:

    • If each page component manages its own state and data fetching independently, there’s no risk of overlapping state updates from multiple requests.

  3. Automatic Cleanup:

    • When a component unmounts, React automatically cleans up its state and effects. Any pending fetch requests will not update the state of an unmounted component, preventing race conditions.


<ol><li><p><strong>Component Unmounting</strong>:</p><ul><li><p>When navigating between pages, if the components are <strong>fully unmounted and remounted</strong> (e.g., using conditional rendering like <code>{page === 'issue' &amp;&amp; &lt;Issue /&gt;}</code>), the old component and its state are completely destroyed.</p></li><li><p>Any pending asynchronous operations (like <code>fetch</code>) in the unmounted component will <strong>not update the state</strong> because the component no longer exists.</p></li></ul></li><li><p><strong>No Shared State</strong>:</p><ul><li><p>If each page component manages its own state and data fetching independently, there’s no risk of overlapping state updates from multiple requests.</p></li></ul></li><li><p><strong>Automatic Cleanup</strong>:</p><ul><li><p>When a component unmounts, React automatically cleans up its state and effects. Any pending <code>fetch</code> requests will not update the state of an unmounted component, preventing race conditions.</p></li></ul></li></ol><p></p>
89
New cards

Fixing race conditions: Drop incorrect result

A much gentler way to solve race conditions, If the result returns the id that was used to generate the url , we can just compare them. And if they don't match, ignore them using Refs. The trick here is to escape the React lifecycle and locally scoped data in functions and get access to the "latest" id inside all iterations of useEffect , even the "stale" ones.

If your results don't return anything that identifies them reliably? No problem, we can just compare the URL instead

<p>A much gentler way to solve race conditions, If the result returns the id that was used to generate the url , we can just compare them. And if they don't match, ignore them using Refs. The trick here is to escape the React lifecycle and locally scoped data in functions and get access to the "latest" id inside all iterations of useEffect , even the "stale" ones.</p><p>If your results don't return anything that identifies them reliably? No problem, we can just compare the URL instead</p>
90
New cards

Fixing race conditions: Drop all previous results

Don't like the previous solution or think that using a ref for something like this is weird? No problem, there is another way. useEffect has something called a "cleanup" function, where we can clean up stuff like subscriptions. Or in our case, it's active fetch requests.

The cleanup function [36] is run after a component is unmounted, or before every re-render with changed dependencies.

The fetch Promise, although async, still exists only within that closure and has access only to the local variables of the started it. So when we check the useEffect run that isActive boolean in the .then callback, only the latest run, the one that hasn't been cleaned up yet, will have the variable set to true . So all we need now is to check whether we're in the active closure, and if yes - set state. If no - do nothing. The data will simply disappear into the void again.

91
New cards

Fixing race conditions: Cancel all previous requests

Instead of cleaning up or comparing results, we can simply cancel all the previous requests. If they never finish, the state update with obsolete data will never happen, and the problem won't exist. We can use the AbortController [39] interface for this.

<p>Instead of cleaning up or comparing results, we can simply cancel all the previous requests. If they never finish, the state update with obsolete data will never happen, and the problem won't exist. We can use the AbortController [39] interface for this.</p>
92
New cards
93
New cards
94
New cards

Simple try/catch in React: how to and caveats

When an error is caught, we need to do something with it, right? So, what exactly can we do, other than logging it somewhere? Or, to be more precise: what can we do for our users? Just leaving them with an empty screen or broken interface is not exactly user-friendly.

The most obvious and intuitive answer would be to render something while we wait for the fix. Luckily, we can do whatever we want in that catch statement, including setting the state. So we can do something. (check img)

This approach is pretty straightforward and works great for simple, predictable, and narrow use cases like catching a failed fetch request. But if you want to catch all errors that can happen in a component, you'll face some challenges and serious limitations.

<p>When an error is caught, we need to do something with it, right? So, what exactly can we do, other than logging it somewhere? Or, to be more precise: what can we do for our users? Just leaving them with an empty screen or broken interface is not exactly user-friendly.</p><p>The most obvious and intuitive answer would be to render something while we wait for the fix. Luckily, we can do whatever we want in that catch statement, including setting the state. So we can do something. <em>(check img)</em></p><p>This approach is pretty straightforward and works great for simple, predictable, and narrow use cases like catching a failed fetch request. But if you want to catch all errors that can happen in a component, you'll face some challenges and serious limitations.</p>
95
New cards

Limitation 1: You will have trouble with the useEffect hook.

If we wrap useEffect with try/catch , it simply won't work (Check img)

96
New cards
97
New cards

Limitation 3: Setting state during render is a no-no

If you're trying to catch errors outside of useEffect and various callbacks (i.e., during the component's render), then dealing with them properly is not that trivial anymore: state updates during render are not allowed


To summarize this section: if we rely solely on try/catch in React, we will either miss most of the errors or turn every component into an incomprehensible mess of code that will probably cause errors by itself.

<p>If you're trying to catch errors outside of useEffect and various callbacks (i.e., during the component's render), then dealing with them properly is not that trivial anymore: state updates during render are not allowed</p><p></p><p>To summarize this section: if we rely solely on try/catch in React, we will either miss most of the errors or turn every component into an incomprehensible mess of code that will probably cause errors by itself.</p>
98
New cards

React ErrorBoyndary component

To mitigate the limitations from above, React gives us what is known as "Error Boundaries" [41]: a special API that turns a regular component into a try/catch statement in a way, only for React declarative code.

<p>To mitigate the limitations from above, React gives us what is known as "Error Boundaries" [41]: a special API that turns a regular component into a try/catch statement in a way, only for React declarative code.</p>
99
New cards
100
New cards