Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow islands to be re-rendered with new props on page transition #10136

Merged
merged 13 commits into from
Mar 8, 2024
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { useState } from 'react';
import './Island.css';
import { indirect} from './css.js';

export default function Counter({ children, count: initialCount, id }) {
export default function Counter({ children, count: initialCount, id, page }) {
const [count, setCount] = useState(initialCount);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);

return (
<>
<div id={id} className="counter">
<h1 className="page">{page}</h1>
<button className="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button className="increment" onClick={add}>+</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import Island from '../components/Island.jsx';
<Layout>
<p id="island-one">Page 1</p>
<a id="click-two" href="/island-two">go to 2</a>
<Island count={5} client:load transition:persist transition:name="counter" />
<Island count={5} page="Island 1" client:load transition:persist transition:name="counter" />
</Layout>
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import Island from '../components/Island.jsx';
<Layout>
<p id="island-two">Page 2</p>
<a id="click-one" href="/island-one">go to 1</a>
<Island count={2} client:load transition:persist transition:name="counter" />
<Island count={2} page="Island 2" client:load transition:persist transition:name="counter" />
</Layout>
8 changes: 6 additions & 2 deletions packages/astro/e2e/view-transitions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,10 @@ test.describe('View Transitions', () => {
cnt = page.locator('.counter pre');
// Count should remain
await expect(cnt).toHaveText('6');

// Props should have changed
const pageTitle = page.locator('.page');
await expect(pageTitle).toHaveText('Island 2');
});

test('Scripts are only executed once', async ({ page, astro }) => {
Expand Down Expand Up @@ -782,7 +786,7 @@ test.describe('View Transitions', () => {
});

test('client:only styles are retained on transition (1/2)', async ({ page, astro }) => {
const totalExpectedStyles = 9;
const totalExpectedStyles = 8;
matthewp marked this conversation as resolved.
Show resolved Hide resolved

await page.goto(astro.resolveUrl('/client-only-one'));
let msg = page.locator('.counter-message');
Expand All @@ -801,7 +805,7 @@ test.describe('View Transitions', () => {
});

test('client:only styles are retained on transition (2/2)', async ({ page, astro }) => {
const totalExpectedStyles_page_three = 11;
const totalExpectedStyles_page_three = 10;
matthewp marked this conversation as resolved.
Show resolved Hide resolved
const totalExpectedStyles_page_four = 9;

await page.goto(astro.resolveUrl('/client-only-three'));
Expand Down
5 changes: 5 additions & 0 deletions packages/astro/src/transitions/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,11 @@ async function updateDOM(
// The element exists in the new page, replace it with the element
// from the old page so that state is preserved.
newEl.replaceWith(el);
// For islands, copy over the props to allow them to re-render
if(newEl.localName === 'astro-island') {
el.setAttribute('ssr', '');
el.setAttribute('props', newEl.getAttribute('props')!);
}
}
}
restoreFocus(savedFocus);
Expand Down
15 changes: 13 additions & 2 deletions packages/integrations/react/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ function getChildren(childString, experimentalReactChildren) {
}
}

// Keep a map of roots so we can reuse them on re-renders
let rootMap = new WeakMap();

export default (element) =>
(Component, props, { default: children, ...slotted }, { client }) => {
if (!element.hasAttribute('ssr')) return;
Expand All @@ -75,13 +78,21 @@ export default (element) =>
}
if (client === 'only') {
return startTransition(() => {
const root = createRoot(element);
let root = rootMap.get(element);
if(!root) {
root = createRoot(element);
rootMap.set(element, root);
}
root.render(componentEl);
element.addEventListener('astro:unmount', () => root.unmount(), { once: true });
});
}
startTransition(() => {
const root = hydrateRoot(element, componentEl, renderOptions);
let root = rootMap.get(element);
if(!root) {
root = hydrateRoot(element, componentEl, renderOptions);
rootMap.set(element, root);
}
root.render(componentEl);
element.addEventListener('astro:unmount', () => root.unmount(), { once: true });
});
Expand Down
Loading