- Getting Started
- The Basics
- Concepts
- SuperNative
-
EDGE Components
- Introduction
- Activity Indicator
- Badge
- Bottom Navigation
- Bottom Sheet
- Button
- Button Group
- Canvas
- Carousel
- Checkbox
- Chip
- Column
- Divider
- Gesture Area
- Icon
- Icons
- Image
- Layout & Styling
- List
- Menus
- Modal
- Pressable
- Progress Bar
- Radio Group
- Row
- Scroll View
- Select
- Shapes
- Side Navigation
- Slider
- Spacer
- Stack
- Tab Row
- Text
- Text Input
- Toggle
- Top Bar
- Virtual List
- Web View
- Plugins
- Testing
- Architecture
You're viewing pre-release documentation — version 4.x is in beta
Features, APIs and behaviour may change before the stable release. View the stable version (3.x)
Subtree Reuse
Declarative UI encourages a simple mental model: describe the whole screen, every time. Your component's
render() doesn't compute a diff — it returns the full Element Tree, whether one
character changed or everything did.
Taken literally, that model would be wasteful: a ticking clock in a top bar would re-encode and re-render an entire screen once a second. Subtree reuse is the set of optimizations that keeps the simple mental model and the performance — and like the rest of the pipeline, it's automatic. You never opt in, and the rendered output is pixel-identical.
Three layers of "don't repeat yourself"#
1. Whole-frame skip. After encoding a frame, the Element Runtime compares it byte-for-byte against the previous one. Identical frames are dropped on the spot — the native side is never even woken. Idle re-renders (a poll that found nothing new, a handler that didn't change state) cost almost nothing.
2. Subtree reuse markers. When a frame does change, most of it usually hasn't. During the render phase, every element computes a compact fingerprint of itself and its children — a content hash. At publish time, any subtree whose fingerprint matches the previous frame is written as a single tiny reuse marker instead of being re-encoded; its children don't appear in the frame at all. The native reader splices the corresponding subtree from the tree it already has, without decoding anything. A one-line text change publishes a handful of nodes, not a screen's worth.
3. Diffing at mount. Whatever does arrive still gets diffed against the previous native tree before touching the UI, so SwiftUI and Compose re-render only the views whose nodes actually differ. Views that survive the diff keep their identity — along with their scroll positions, focus and running animations.
Helping the diff: keys#
Reuse works by matching nodes between frames, and matching needs identity. By default, elements are identified by their position in the tree, which is perfect for static structure. For dynamic lists, give elements a key — just like you would in Livewire or React:
@foreach ($messages as $message) <native:column :native:key="$message->id" class="bg-theme-surface rounded-lg p-3"> <native:text>{{ $message->body }}</native:text> </native:column>@endforeach
With keys, inserting a message at the top of the list is understood as one new card rather than every card changed — the other subtrees match their fingerprints from the previous frame and travel as reuse markers.
Free, by design#
Fingerprinting happens as part of the normal render walk, the byte comparison as part of the normal publish, and the splice as part of the normal decode — there's no separate "optimization pass" burning cycles. And because all three layers live in the shared core, iOS and Android benefit identically, on every frame, by default.
in no time