Skip to content

Commit

Permalink
fix: ref inside context
Browse files Browse the repository at this point in the history
  • Loading branch information
Varixo committed Dec 5, 2024
1 parent 45de4be commit eaa1604
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-flowers-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': patch
---

fix: using ref inside useContext
2 changes: 1 addition & 1 deletion packages/qwik/src/core/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ export abstract class _SharedContainer implements Container {
};
} | null, DomRefConstructor: {
new (...rest: any[]): {
id: string;
$ssrNode$: ISsrNode;
};
} | null, symbolToChunkResolver: SymbolToChunkResolver, writer?: StreamWriter, prepVNodeData?: (vNode: any) => void): SerializationContext;
// (undocumented)
Expand Down
4 changes: 2 additions & 2 deletions packages/qwik/src/core/shared/shared-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ValueOrPromise } from './utils/types';
import { version } from '../version';
import type { Effect, EffectData } from '../signal/signal';
import type { Signal } from '../signal/signal.public';
import type { StreamWriter, SymbolToChunkResolver } from '../ssr/ssr-types';
import type { ISsrNode, StreamWriter, SymbolToChunkResolver } from '../ssr/ssr-types';
import type { Scheduler } from './scheduler';
import { createScheduler } from './scheduler';
import { createSerializationContext, type SerializationContext } from './shared-serialization';
Expand Down Expand Up @@ -50,7 +50,7 @@ export abstract class _SharedContainer implements Container {
new (...rest: any[]): { nodeType: number; id: string };
} | null,
DomRefConstructor: {
new (...rest: any[]): { id: string };
new (...rest: any[]): { $ssrNode$: ISsrNode };
} | null,
symbolToChunkResolver: SymbolToChunkResolver,
writer?: StreamWriter,
Expand Down
23 changes: 14 additions & 9 deletions packages/qwik/src/core/shared/shared-serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
getStoreTarget,
isStore,
} from '../signal/store';
import type { SymbolToChunkResolver } from '../ssr/ssr-types';
import type { ISsrNode, SymbolToChunkResolver } from '../ssr/ssr-types';
import { untrack } from '../use/use-core';
import { createResourceReturn, type ResourceReturnInternal } from '../use/use-resource';
import { Task, isTask } from '../use/use-task';
Expand Down Expand Up @@ -577,9 +577,11 @@ type SsrNode = {
};

type DomRef = {
id: string;
$ssrNode$: SsrNode;
};

let isDomRef = (obj: unknown): obj is DomRef => false;

export interface SerializationContext {
$serialize$: () => void;

Expand Down Expand Up @@ -654,7 +656,7 @@ export const createSerializationContext = (
} | null,
/** DomRef constructor, for instanceof checks. */
DomRefConstructor: {
new (...rest: any[]): { id: string };
new (...rest: any[]): { $ssrNode$: ISsrNode };
} | null,
symbolToChunkResolver: SymbolToChunkResolver,
getProp: (obj: any, prop: string) => any,
Expand Down Expand Up @@ -689,9 +691,9 @@ export const createSerializationContext = (
const isSsrNode = (NodeConstructor ? (obj) => obj instanceof NodeConstructor : () => false) as (
obj: unknown
) => obj is SsrNode;
const isDomRef = (
DomRefConstructor ? (obj) => obj instanceof DomRefConstructor : () => false
) as (obj: unknown) => obj is DomRef;
isDomRef = (DomRefConstructor ? (obj) => obj instanceof DomRefConstructor : () => false) as (
obj: unknown
) => obj is DomRef;

return {
$serialize$(): void {
Expand Down Expand Up @@ -839,8 +841,8 @@ export const createSerializationContext = (
discoveredValues.push(obj.$el$, obj.$qrl$, obj.$state$, obj.$effectDependencies$);
} else if (isSsrNode(obj)) {
discoveredValues.push(obj.vnodeData);
} else if (isDomRef(obj)) {
discoveredValues.push(obj.id);
} else if (isDomRef!(obj)) {
discoveredValues.push(obj.$ssrNode$.id);
} else if (isJSXNode(obj)) {
discoveredValues.push(obj.type, obj.props, obj.constProps, obj.children);
} else if (Array.isArray(obj)) {
Expand Down Expand Up @@ -1113,7 +1115,8 @@ function serialize(serializationContext: SerializationContext): void {
output(TypeIds.Object, out);
}
} else if ($isDomRef$(value)) {
output(TypeIds.RefVNode, value.id);
value.$ssrNode$.vnodeData[0] |= VNodeDataFlag.SERIALIZE;
output(TypeIds.RefVNode, value.$ssrNode$.id);
} else if (value instanceof Signal) {
/**
* Special case: when a Signal value is an SSRNode, it always needs to be a DOM ref instead.
Expand Down Expand Up @@ -1538,6 +1541,8 @@ export const canSerialize = (value: any): boolean => {
return true;
} else if (value instanceof Uint8Array) {
return true;
} else if (isDomRef?.(value)) {
return true;
}
} else if (typeof value === 'function') {
if (isQrl(value) || isQwikComponent(value)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface Container {
new (...rest: any[]): { nodeType: number; id: string };
} | null,
DomRefConstructor: {
new (...rest: any[]): { id: string };
new (...rest: any[]): { $ssrNode$: ISsrNode };
} | null,
symbolToChunkResolver: SymbolToChunkResolver,
writer?: StreamWriter
Expand Down
75 changes: 75 additions & 0 deletions packages/qwik/src/core/tests/ref.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {
Fragment as Component,
component$,
createContextId,
useContext,
useContextProvider,
useSignal,
useStore,
useVisibleTask$,
Expand Down Expand Up @@ -129,4 +132,76 @@ describe.each([
expect(isElement((globalThis as any).element[0])).toBeTruthy();
(globalThis as any).element = undefined;
});

describe('should serialize refs inside context', () => {
it('should serialize refs from child component', async () => {
(globalThis as any).element = [] as HTMLElement[];

const contextId = createContextId('test');

const Child = component$(() => {
const store = useContext<any>(contextId);
return <span ref={(element) => store.refs.push(element)}></span>;
});

const Parent = component$(() => {
const store = useStore({
refs: [],
});
useContextProvider(contextId, store);
useVisibleTask$(() => {
(globalThis as any).element.push(store.refs[0]);
});
return (
<div>
<Child />
</div>
);
});

const { document } = await render(<Parent />, { debug });

if (ssrRenderToDom === render) {
await trigger(document.body, 'div', 'qvisible');
}

expect(isElement((globalThis as any).element[0])).toBeTruthy();
(globalThis as any).element = undefined;
});

it('should serialize refs from parent component', async () => {
(globalThis as any).element = [] as HTMLElement[];

const contextId = createContextId('test');

const Child = component$(() => {
useContext<any>(contextId);
return <span></span>;
});

const Parent = component$(() => {
const store = useStore<any>({
refs: [],
});
useContextProvider(contextId, store);
useVisibleTask$(() => {
(globalThis as any).element.push(store.refs[0]);
});
return (
<div ref={(element) => store.refs.push(element)}>
<Child />
</div>
);
});

const { document } = await render(<Parent />, { debug });

if (ssrRenderToDom === render) {
await trigger(document.body, 'div', 'qvisible');
}

expect(isElement((globalThis as any).element[0])).toBeTruthy();
(globalThis as any).element = undefined;
});
});
});
4 changes: 2 additions & 2 deletions packages/qwik/src/server/ssr-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1126,10 +1126,10 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
if (key === 'ref') {
const lastNode = this.getLastNode();
if (isSignal(value)) {
value.value = new DomRef(lastNode.id);
value.value = new DomRef(lastNode);
continue;
} else if (typeof value === 'function') {
value(new DomRef(lastNode.id));
value(new DomRef(lastNode));
continue;
}
}
Expand Down
3 changes: 1 addition & 2 deletions packages/qwik/src/server/ssr-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ export class SsrNode implements ISsrNode {

/** A ref to a DOM element */
export class DomRef {
/** @id is the same id as for SsrNode */
constructor(public id: string) {}
constructor(public $ssrNode$: ISsrNode) {}
}

export type SsrNodeType = 1 | 3 | 9 | 11;
Expand Down

0 comments on commit eaa1604

Please sign in to comment.