Skip to content

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, all classes are overwritten. This also removes other classes that have been added manually.

In the example below, updating someSignal will restore "foo", remove "bar" and set, whatever is in someSignal:

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.