@@ -4,8 +4,14 @@ import type { StreamWriter } from '../../server/types';
4
4
import { VNodeDataFlag } from '../../server/types' ;
5
5
import type { VNodeData } from '../../server/vnode-data' ;
6
6
import { type DomContainer } from '../client/dom-container' ;
7
- import type { VNode } from '../client/types' ;
8
- import { vnode_getNode , vnode_isVNode , vnode_locate , vnode_toString } from '../client/vnode' ;
7
+ import type { ElementVNode , VNode } from '../client/types' ;
8
+ import {
9
+ ensureMaterialized ,
10
+ vnode_getNode ,
11
+ vnode_isVNode ,
12
+ vnode_locate ,
13
+ vnode_toString ,
14
+ } from '../client/vnode' ;
9
15
import { isSerializerObj } from '../reactive-primitives/utils' ;
10
16
import type { AsyncComputeQRL , SerializerArg } from '../reactive-primitives/types' ;
11
17
import {
@@ -579,6 +585,32 @@ const allocate = (container: DeserializeContainer, typeId: number, value: unknow
579
585
case TypeIds . RefVNode :
580
586
const vNode = retrieveVNodeOrDocument ( container , value ) ;
581
587
if ( vnode_isVNode ( vNode ) ) {
588
+ /**
589
+ * If we have a ref, we need to ensure the element is materialized.
590
+ *
591
+ * Example:
592
+ *
593
+ * ```
594
+ * const Cmp = component$(() => {
595
+ * const element = useSignal<HTMLDivElement>();
596
+ *
597
+ * useVisibleTask$(() => {
598
+ * element.value!.innerHTML = 'I am the innerHTML content!';
599
+ * });
600
+ *
601
+ * return (
602
+ * <div ref={element} />
603
+ * );
604
+ * });
605
+ * ```
606
+ *
607
+ * If we don't materialize early element with ref property, and change element innerHTML it
608
+ * will be applied to a vnode tree during the lazy materialization, and it is wrong.
609
+ *
610
+ * Next if we rerender component it will remove applied innerHTML, because the system thinks
611
+ * it is a part of the vnode tree.
612
+ */
613
+ ensureMaterialized ( vNode as ElementVNode ) ;
582
614
return vnode_getNode ( vNode ) ;
583
615
} else {
584
616
throw qError ( QError . serializeErrorExpectedVNode , [ typeof vNode ] ) ;
0 commit comments