Skip to content

Commit

Permalink
fix: handle preact signals in array correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
ph1p committed Aug 25, 2024
1 parent a3b9b91 commit b9fa93a
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/eleven-cameras-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/preact': major
---

Preact signals are now serialized correctly in arrays when they are given to components.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { h } from 'preact';

export default ({ signalsArray }) => {
return <div class="preact-signal-array">
<h1>{signalsArray[0]} {signalsArray[3]}</h1>
<p>{signalsArray[1].value}{signalsArray[2].value}{signalsArray[4].value}</p>
</div>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
import { signal } from '@preact/signals';
import Signals from '../components/Signals';
import SignalsInArray from '../components/SignalsInArray';
const count = signal(1);
const secondCount = signal(2);
---
<html>
<head>
Expand All @@ -10,5 +12,6 @@ const count = signal(1);
<body>
<Signals client:load count={count} />
<Signals client:load count={count} />
<SignalsInArray client:load signalsArray={["I'm not a signal", count, count, 12345, secondCount]} />
</body>
</html>
22 changes: 22 additions & 0 deletions packages/astro/test/preact-component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,26 @@ describe('Preact component', () => {
assert.notEqual(sigs1.count, undefined);
assert.equal(sigs1.count, sigs2.count);
});

it('Can use signals in array', async () => {
const html = await fixture.readFile('/signals/index.html');
const $ = cheerio.load(html);
const element = $('.preact-signal-array');
assert.equal(element.length, 1);

const sigs1Raw = $($('astro-island')[2]).attr('data-preact-signals');

const sigs1 = JSON.parse(sigs1Raw);

assert.deepEqual(sigs1, {
signalsArray: [
['p0', 1],
['p0', 2],
['p1', 4],
],
});

assert.equal($('.preact-signal-array h1').text(), "I'm not a signal 12345");
assert.equal($('.preact-signal-array p').text(), '112');
});
});
23 changes: 18 additions & 5 deletions packages/integrations/preact/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,26 @@ export default (element: HTMLElement) =>
let signalsRaw = element.dataset.preactSignals;
if (signalsRaw) {
const { signal } = await import('@preact/signals');
let signals: Record<string, string> = JSON.parse(element.dataset.preactSignals!);
let signals: Record<string, string | { id: string; i: number }[]> = JSON.parse(
element.dataset.preactSignals!,
);
for (const [propName, signalId] of Object.entries(signals)) {
if (!sharedSignalMap.has(signalId)) {
const signalValue = signal(props[propName]);
sharedSignalMap.set(signalId, signalValue);
if (Array.isArray(signalId)) {
signalId.forEach(({ id, i }) => {
const [valueOfSignal, indexInProps] = props[propName][i];
if (!sharedSignalMap.has(id)) {
const signalValue = signal(valueOfSignal);
sharedSignalMap.set(id, signalValue);
}
props[propName][indexInProps] = sharedSignalMap.get(id);
});
} else {
if (!sharedSignalMap.has(signalId)) {
const signalValue = signal(props[propName]);
sharedSignalMap.set(signalId, signalValue);
}
props[propName] = sharedSignalMap.get(signalId);
}
props[propName] = sharedSignalMap.get(signalId);
}
}

Expand Down
35 changes: 25 additions & 10 deletions packages/integrations/preact/src/signals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,41 @@ export function serializeSignals(
map: PropNameToSignalMap,
) {
// Check for signals
const signals: Record<string, string> = {};
const signals: Record<string, string | [string, number][]> = {};
for (const [key, value] of Object.entries(props)) {
if (isSignal(value)) {
if (Array.isArray(value)) {
value.forEach((signal, index) => {
// find signals in array. The index is important!
if (isSignal(signal)) {
props[key] = props[key].map((v: SignalLike, i: number) =>
i === index ? [signal.peek(), i] : v,
);

map.set(key, [...((map.get(key) || []) as []), [signal, index]]);
signals[key] = [...((signals[key] || []) as []), [getSignalId(ctx, signal), index]];
}
});
} else if (isSignal(value)) {
// Set the value to the current signal value
// This mutates the props on purpose, so that it will be serialized correct.
props[key] = value.peek();
map.set(key, value);

let id: string;
if (ctx.signals.has(value)) {
id = ctx.signals.get(value)!;
} else {
id = incrementId(ctx);
ctx.signals.set(value, id);
}
signals[key] = id;
signals[key] = getSignalId(ctx, value);
}
}

if (Object.keys(signals).length) {
attrs['data-preact-signals'] = JSON.stringify(signals);
}
}

function getSignalId(ctx: Context, item: SignalLike) {
let id = ctx.signals.get(item);
if (!id) {
id = incrementId(ctx);
ctx.signals.set(item, id);
}

return id;
}
2 changes: 1 addition & 1 deletion packages/integrations/preact/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type SignalLike = {
peek(): any;
};

export type PropNameToSignalMap = Map<string, SignalLike>;
export type PropNameToSignalMap = Map<string, SignalLike | [SignalLike, number][]>;

export type AstroPreactAttrs = {
['data-preact-signals']?: string;
Expand Down

0 comments on commit b9fa93a

Please sign in to comment.