From 164dc4aea94a1f8b887781bdbfed454420e40207 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 6 Aug 2025 16:50:46 -0700 Subject: [PATCH 1/6] Add experimental documentation for `VirtualView` --- docs/virtualview.md | 169 ++++++++++++++++++ .../docs/assets/d_virtualview_modes.svg | 2 + 2 files changed, 171 insertions(+) create mode 100644 docs/virtualview.md create mode 100644 website/static/docs/assets/d_virtualview_modes.svg diff --git a/docs/virtualview.md b/docs/virtualview.md new file mode 100644 index 00000000000..38cb9f6a28e --- /dev/null +++ b/docs/virtualview.md @@ -0,0 +1,169 @@ +--- +id: virtualview +title: VirtualView ⚗️ +--- + +:::important +**This API is experimental.** Experimental APIs may contain bugs and are likely to change in a future version of React Native. Don't use them in production. +::: + +`VirtualView` is a core component that behaves similar to [`View`](view). + +When it is the descendent of a [`ScrollView`](scrollview), it gains additional virtualization capabilities to reduce its memory footprint when obscured by the scroll viewport. + +```tsx + + + Hello world! + + +``` + +A `VirtualView` without an ancestor `ScrollView` does not have any virtualization capabilities. + +## Virtualization + +When a `VirtualView` leaves the visible region of a [`ScrollView`](scrollview), it becomes hidden. When hidden, a `VirtualView` will cache its most recent layout and may unmount its children — a process called virtualization. + +When a `VirtualView` returns to the visible region of a `ScrollView`, it becomes visible. When visible, its children are *guaranteed* to be rendered. This guarantee is maintained by blocking the main thread from rendering the next frame that would reveal the `VirtualView` until its children can be rendered. + +Diagram of VirtualView modes and thresholds. + +:::note +In future developments, a hidden `VirtualView` may instead render its children in an [``](https://react.dev/reference/react/Activity) to preserve state for as long as possible while balancing memory overhead. +::: + +### Blocking the Main Thread + +This is the first time in React Native’s feature set where rendering a React component can block the main thread. This is a new capability enabled by the [New Architecture](/architecture/landing-page)! + +Blocking the main thread can provide a better user experience by preventing flashes of blank frames that sometimes occurwhen using components like [`FlatList`](flatlist). It can also enable better performance by using main thread priority, which is also typically run on higher performance cores. + +However, blocking the main thread also comes with tradeoffs. If an update operation, such as mounting the children of a `VirtualView`, takes too long to finish, it can now drop frames. Dropping more than a couple frames can lead to a worse user experience by making the app feel sluggish and non-responsive. Dropping too many frames may cause the operating system to display a modal indicating the app is not responsive, or it may even terminate your app! + +### Prerendering + +`VirtualView` enables you to benefit from main thread rendering while mitigating the disadvantages of dropped frames by rendering earlier before it is needed. This is called “prerendering”. + +By default, each `VirtualView` will prerender its children when it approaches the visible region of a [`ScrollView`](scrollview). When this happens, its children will be rendered on a background thread at a lower priority (using a [transition](https://react.dev/reference/react/startTransition)). This ensures that the main thread and React are available to handle other critical user interactions at a higher priority. + +:::note +`VirtualView`'s prerender logic is not currently configurable. The algorithm for determining this is undergoing active design iteration and is likely to change in a future release. +::: + +--- + +## Props + +### `children` + +Content to render inside this `VirtualView`. + +| Type | +| ------------------------ | +| [React Node](react-node) | + +--- + +### **`onModeChange`** + +Invoked when the `VirtualView` changes how it renders its children. + +If a callback is supplied, it may be invoked from different threads and priorities depending on the internal state change. This can be detected by checking the `mode` property on the event: + +- If `mode` is `VirtualViewMode.Visible`, the callback is being invoked from the main thread with immediate priority. +- If `mode` is `VirtualViewMode.Prerender` or `VirtualViewMode.Hidden`, the callback is being invoked from a background thread with transition priority. + +The callback will never be invoked consecutively with the same `mode` value. However, there are few guarantees about sequencing of events. Also, the callback may never be invoked with `VirtualViewMode.Visible` even if it becomes visible, if the children were successfully prerendered. + +| Type | +| -------------------------------------------------- | +| `md ([ModeChangeEvent](#modechangeevent)) => void` | + +--- + +### `nativeID` + +An identifier for locating this view from native classes. + +| Type | +| ------ | +| string | + +--- + +### `style` + +| Type | +| ------------------------------ | +| [View Style](view-style-props) | + +--- + +## Type Definitions + +### `ModeChangeEvent` + +Argument supplied to [`onModeChange`](#onmodechange). + +| Type | +| ------ | +| object | + +**Properties:** + +| Name | Type | Description | +| ------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------- | +| mode | [VirtualViewMode](#virtualviewmode) | New mode of the `VirtualView`. | +| target | element | `VirtualView` emitting this event. | +| targetRect | [Rect](rect) | Layout of `target` relative to the nearest ancestor `ScrollView`. | +| thresholdRect | [Rect](rect) | Layout of the threshold that triggered this event, relative to the nearest ancestor `ScrollView`. | + +:::note +For example, if a `VirtualView` enters the visible region of a [`ScrollView`](scrollview)... + +- `mode` would be `VirtualViewMode.Visible` +- `thresholdRect` would describe the current scroll position of the nearest ancestor [`ScrollView`](scrollview) +- `targetRect` would be the layout of `target` that overlaps with `thresholdRect` (i.e. it is within the visible region of the [`ScrollView`](scrollview)) + +::: + +### `VirtualViewMode` + +Possible modes of a `VirtualView`. + +| Name | Value | Description | +| --------- | ----- | ---------------------------------------------- | +| Visible | `0` | Target view is visible. | +| Prerender | `1` | Target view is hidden, but can be prerendered. | +| Hidden | `2` | Target view is hidden. | + +--- + +## Static Methods + +### `createHiddenVirtualView()` + +```tsx +static createHiddenVirtualView(height: number): typeof VirtualView; +``` + +`VirtualView` initially renders its children as visible, even if it is initially obscured by an ancestor [`ScrollView`](scrollview). This is because when a component is initially rendered, the presence of an ancestor [`ScrollView`](scrollview) — let alone its size and scroll position — are unknown. + +For advanced use cases, `createHiddenVirtualView` creates a component that renders an initially hidden `VirtualView` with the supplied estimated layout. + +```tsx +const HiddenVirtualView = createHiddenVirtualView(100); + + + + Hello world! + + +``` + +**Parameters:** + +| Name | Type | Description | +| ---------------------------------------------------------- | ------ | ------------------------------------------------------ | +| height
Required
| number | Estimated height of initially rendering `VirtualView`. | diff --git a/website/static/docs/assets/d_virtualview_modes.svg b/website/static/docs/assets/d_virtualview_modes.svg new file mode 100644 index 00000000000..a0df4710f80 --- /dev/null +++ b/website/static/docs/assets/d_virtualview_modes.svg @@ -0,0 +1,2 @@ +VisiblethresholdrectHiddenPrerenderVisiblePrerenderthresholdRefctScrollViewContent Container \ No newline at end of file From 0acf2f2bb901df648e56f0950426cad2ef47364d Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 6 Aug 2025 16:54:08 -0700 Subject: [PATCH 2/6] Revert unnecessary bold formatting --- docs/virtualview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/virtualview.md b/docs/virtualview.md index 38cb9f6a28e..bd3dc89ac35 100644 --- a/docs/virtualview.md +++ b/docs/virtualview.md @@ -65,7 +65,7 @@ Content to render inside this `VirtualView`. --- -### **`onModeChange`** +### `onModeChange` Invoked when the `VirtualView` changes how it renders its children. From 870e4e7a1448fd44dad72c08401103e780256c51 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 6 Aug 2025 16:58:11 -0700 Subject: [PATCH 3/6] Fix missing hyperlinks and minor nits --- docs/virtualview.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/virtualview.md b/docs/virtualview.md index bd3dc89ac35..761eab52ed7 100644 --- a/docs/virtualview.md +++ b/docs/virtualview.md @@ -19,13 +19,13 @@ When it is the descendent of a [`ScrollView`](scrollview), it gains additional v ``` -A `VirtualView` without an ancestor `ScrollView` does not have any virtualization capabilities. +A `VirtualView` without an ancestor[`ScrollView`](scrollview) does not have any virtualization capabilities. ## Virtualization When a `VirtualView` leaves the visible region of a [`ScrollView`](scrollview), it becomes hidden. When hidden, a `VirtualView` will cache its most recent layout and may unmount its children — a process called virtualization. -When a `VirtualView` returns to the visible region of a `ScrollView`, it becomes visible. When visible, its children are *guaranteed* to be rendered. This guarantee is maintained by blocking the main thread from rendering the next frame that would reveal the `VirtualView` until its children can be rendered. +When a `VirtualView` returns to the visible region of a [`ScrollView`](scrollview), it becomes visible. When visible, its children are *guaranteed* to be rendered. This guarantee is maintained by blocking the main thread from rendering the next frame that would reveal the `VirtualView` until its children can be rendered. Diagram of VirtualView modes and thresholds. @@ -71,10 +71,10 @@ Invoked when the `VirtualView` changes how it renders its children. If a callback is supplied, it may be invoked from different threads and priorities depending on the internal state change. This can be detected by checking the `mode` property on the event: -- If `mode` is `VirtualViewMode.Visible`, the callback is being invoked from the main thread with immediate priority. -- If `mode` is `VirtualViewMode.Prerender` or `VirtualViewMode.Hidden`, the callback is being invoked from a background thread with transition priority. +- If `mode` is [`VirtualViewMode.Visible`](#virtualviewmode), the callback is being invoked from the main thread with immediate priority. +- If `mode` is [`VirtualViewMode.Prerender`](#virtualviewmode) or [`VirtualViewMode.Hidden`](#virtualviewmode), the callback is being invoked from a background thread with transition priority. -The callback will never be invoked consecutively with the same `mode` value. However, there are few guarantees about sequencing of events. Also, the callback may never be invoked with `VirtualViewMode.Visible` even if it becomes visible, if the children were successfully prerendered. +The callback will never be invoked consecutively with the same `mode` value. However, there are few guarantees about sequencing of events. Also, the callback may never be invoked with [`VirtualViewMode.Visible`](#virtualviewmode) even if it becomes visible, if the children were successfully prerendered. | Type | | -------------------------------------------------- | @@ -122,7 +122,7 @@ Argument supplied to [`onModeChange`](#onmodechange). :::note For example, if a `VirtualView` enters the visible region of a [`ScrollView`](scrollview)... -- `mode` would be `VirtualViewMode.Visible` +- `mode` would be [`VirtualViewMode.Visible`](#virtualviewmode) - `thresholdRect` would describe the current scroll position of the nearest ancestor [`ScrollView`](scrollview) - `targetRect` would be the layout of `target` that overlaps with `thresholdRect` (i.e. it is within the visible region of the [`ScrollView`](scrollview)) @@ -150,7 +150,7 @@ static createHiddenVirtualView(height: number): typeof VirtualView; `VirtualView` initially renders its children as visible, even if it is initially obscured by an ancestor [`ScrollView`](scrollview). This is because when a component is initially rendered, the presence of an ancestor [`ScrollView`](scrollview) — let alone its size and scroll position — are unknown. -For advanced use cases, `createHiddenVirtualView` creates a component that renders an initially hidden `VirtualView` with the supplied estimated layout. +For advanced use cases, `createHiddenVirtualView()` creates a component that renders an initially hidden `VirtualView` with the supplied estimated layout. ```tsx const HiddenVirtualView = createHiddenVirtualView(100); From b268c53b76d7a43552e57eb63fb709adbf32f2c7 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 6 Aug 2025 17:06:37 -0700 Subject: [PATCH 4/6] Format with Prettier --- docs/virtualview.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/virtualview.md b/docs/virtualview.md index 761eab52ed7..942f9c7d1b5 100644 --- a/docs/virtualview.md +++ b/docs/virtualview.md @@ -25,7 +25,7 @@ A `VirtualView` without an ancestor[`ScrollView`](scrollview) does not have any When a `VirtualView` leaves the visible region of a [`ScrollView`](scrollview), it becomes hidden. When hidden, a `VirtualView` will cache its most recent layout and may unmount its children — a process called virtualization. -When a `VirtualView` returns to the visible region of a [`ScrollView`](scrollview), it becomes visible. When visible, its children are *guaranteed* to be rendered. This guarantee is maintained by blocking the main thread from rendering the next frame that would reveal the `VirtualView` until its children can be rendered. +When a `VirtualView` returns to the visible region of a [`ScrollView`](scrollview), it becomes visible. When visible, its children are _guaranteed_ to be rendered. This guarantee is maintained by blocking the main thread from rendering the next frame that would reveal the `VirtualView` until its children can be rendered. Diagram of VirtualView modes and thresholds. @@ -159,11 +159,11 @@ const HiddenVirtualView = createHiddenVirtualView(100); Hello world! - +; ``` **Parameters:** -| Name | Type | Description | -| ---------------------------------------------------------- | ------ | ------------------------------------------------------ | -| height
Required
| number | Estimated height of initially rendering `VirtualView`. | +| Name | Type | Description | +| ------------------------------------------------------- | ------ | ------------------------------------------------------ | +| height
Required
| number | Estimated height of initially rendering `VirtualView`. | From c33e84275a89d413d464230a81b918a371d574fd Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 7 Aug 2025 09:15:56 -0700 Subject: [PATCH 5/6] Update docs/virtualview.md Fix space --- docs/virtualview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/virtualview.md b/docs/virtualview.md index 942f9c7d1b5..aacde4f02f6 100644 --- a/docs/virtualview.md +++ b/docs/virtualview.md @@ -37,7 +37,7 @@ In future developments, a hidden `VirtualView` may instead render its children i This is the first time in React Native’s feature set where rendering a React component can block the main thread. This is a new capability enabled by the [New Architecture](/architecture/landing-page)! -Blocking the main thread can provide a better user experience by preventing flashes of blank frames that sometimes occurwhen using components like [`FlatList`](flatlist). It can also enable better performance by using main thread priority, which is also typically run on higher performance cores. +Blocking the main thread can provide a better user experience by preventing flashes of blank frames that sometimes occur when using components like [`FlatList`](flatlist). It can also enable better performance by using main thread priority, which is also typically run on higher performance cores. However, blocking the main thread also comes with tradeoffs. If an update operation, such as mounting the children of a `VirtualView`, takes too long to finish, it can now drop frames. Dropping more than a couple frames can lead to a worse user experience by making the app feel sluggish and non-responsive. Dropping too many frames may cause the operating system to display a modal indicating the app is not responsive, or it may even terminate your app! From 529bff1a587bf2c4ba1f8adbffb09ad6f59a5d94 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Mon, 11 Aug 2025 13:16:22 -0700 Subject: [PATCH 6/6] Clarify description for `thresholdRect` --- docs/virtualview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/virtualview.md b/docs/virtualview.md index aacde4f02f6..4357beb34e1 100644 --- a/docs/virtualview.md +++ b/docs/virtualview.md @@ -123,7 +123,7 @@ Argument supplied to [`onModeChange`](#onmodechange). For example, if a `VirtualView` enters the visible region of a [`ScrollView`](scrollview)... - `mode` would be [`VirtualViewMode.Visible`](#virtualviewmode) -- `thresholdRect` would describe the current scroll position of the nearest ancestor [`ScrollView`](scrollview) +- `thresholdRect` would describe the visible viewport of the nearest ancestor [`ScrollView`](scrollview) - `targetRect` would be the layout of `target` that overlaps with `thresholdRect` (i.e. it is within the visible region of the [`ScrollView`](scrollview)) :::