External Mutations
Rvx is built on the assumption that the rendered DOM tree is never mutated externally. This results in some limitations that are outlined on this page.
Common use cases for manual DOM mutation are the embedding of third party libraries or things that rvx just can't do.
Attributes & Properties
Attributes and properties can be safely updated manually. When a signal involved in setting a value is updated, that value is overwritten.
In the example below, updating someSignal
will overwrite only the "alt"
attribute:
const elem = <img src="./a.webp" alt={someSignal} /> as HTMLElement;
elem.src = "./b.webp";
elem.alt = "Something else...";
const elem = e("img").set("src", "./a.webp").set("alt", someSignal).elem;
elem.src = "./b.webp";
elem.alt = "Something else...";
class
When any signal used in the class
attribute is updated, only the affected tokens are added or removed.
In the example below, updating someSignal
has no effect on other tokens.
const elem = <div class={["foo", someSignal]} /> as HTMLElement;
elem.classList.remove("foo");
elem.classList.add("bar");
const elem = e("div").class(["foo", someSignal]).elem;
elem.classList.remove("foo");
elem.classList.add("bar");
style
When any signal used in the style
attribute is updated, only the affected property is overwritten.
In the example below, updating someSignal
will only overwrite the "color"
property:
const elem = <div style={{ color: someSignal }} /> as HTMLElement;
elem.style.color = "red";
elem.style.display = "block";
const elem = e("div").style({ color: someSignal }).elem;
elem.style.color = "red";
elem.style.display = "block";
Nodes
Info
The term logical node in this section refers to anything that was used as content such as an element, a text node or an entire view.
Each line of content in the example below represents a logical node:
render(<>
Hello World!
<some-element />
<Show when={...}>...</Show>
</>);
render([
"Hello World!",
e("some-element"),
Show({ when: ... }),
])
The concept of logical nodes doesn't exist at runtime and is only a means of explanation.
Warning
This section only covers the content of elements, content returned from components and views created with the mount
or render
function.
It's never safe to mutate the children of other views like <Show>
or <For>
.
Inserting Nodes
Arbitrary nodes can always be safely inserted between logical children. In the case of elements, nodes can also be inserted as first or last child.
The example below shows safe and unsafe positions for inserting nodes:
render(<>
{/* unsafe */}
A
{/* safe */}
B
{/* unsafe */}
</>)
<div>
{/* safe */}
A
{/* safe */}
B
{/* safe */}
</div>
render([
// unsafe
"A",
// safe
"B",
// unsafe
])
e("div").append(
// safe
"A",
// safe
"B",
// safe
)
Removing Nodes
Entire logical nodes can be safely removed if they are not the first or last one. In the case of elements, the first and last logical nodes can also be safely removed.
It's also safe to remove any nodes that have been manually inserted.
The example below shows which logical nodes are safe and which are unsafe to remove:
render(<>
A {/* unsafe */}
B {/* safe */}
C {/* unsafe */}
</>)
<div>
A {/* safe */}
B {/* safe */}
C {/* safe */}
</div>
render([
"A", // unsafe
"B", // safe
"C", // unsafe
])
e("div").append(
"A", // safe
"B", // safe
"C", // safe
)
Warning
Removing nodes doesn't affect their lifecycle in any way and signal updates for removed nodes are still processed.
Warning
When removing entire views, you need to make sure that all nodes of that view remain in one same parent node at all time as explained here.
Moving Nodes
Moving nodes is safe, if removing them from their current position and inserting them at the new position is safe.
Referencing Logical Nodes
Since the concept of logical nodes doesn't exist at runtime, you need to convert them into views using the render
function. A view's first
and last
properties always refer to the respective DOM nodes of that view.
function SomeComponent() {
const nodeB = render(<>B</>);
console.log(nodeB.first, nodeB.last);
return <>
A
{nodeB}
D
</>;
}
function SomeComponent() {
const nodeB = render("B");
console.log(nodeB.first, nodeB.last);
return [
"A",
nodeB,
"B",
];
}
Tip
You can assume, that the first
and last
properties always refer to an arbitrary node since views are never empty.