Skip to content

Commit

Permalink
fix: hydration error with useId (#1109)
Browse files Browse the repository at this point in the history
close #1100 

- [x] add failing test
- [x] fix it
  • Loading branch information
dai-shi authored Dec 31, 2024
1 parent 57b7232 commit 67775de
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 23 deletions.
9 changes: 7 additions & 2 deletions e2e/fixtures/ssr-basic/src/components/Counter.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
'use client';

import { useState } from 'react';
import { useId, useState } from 'react';

export const Counter = () => {
const [count, setCount] = useState(0);
const id = useId();
return (
<div
data-testid="counter"
style={{ border: '3px blue dashed', margin: '1em', padding: '1em' }}
>
<p data-testid="count">{count}</p>
<button data-testid="increment" onClick={() => setCount((c) => c + 1)}>
<button
id={id}
data-testid="increment"
onClick={() => setCount((c) => c + 1)}
>
Increment
</button>
</div>
Expand Down
12 changes: 12 additions & 0 deletions e2e/ssr-basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,17 @@ for (const mode of ['DEV', 'PRD'] as const) {
await page.close();
await context.close();
});

test('check hydration error', async ({ page }) => {
test.skip(mode !== 'DEV');
const messages: string[] = [];
page.on('console', (msg) => messages.push(msg.text()));
await page.goto(`http://localhost:${port}/`);
await expect(page.getByTestId('app-name')).toHaveText('Waku');
await expect(page.getByTestId('count')).toHaveText('0');
await page.getByTestId('increment').click();
await expect(page.getByTestId('count')).toHaveText('1');

Check failure on line 70 in e2e/ssr-basic.spec.ts

View workflow job for this annotation

GitHub Actions / E2E on ubuntu-latest (Node 18.17.0) - (3/4)

[firefox] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error

1) [firefox] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error ─────────────────── Error: Timed out 10000ms waiting for expect(locator).toHaveText(expected) Locator: getByTestId('count') Expected string: "1" Received string: "0" Call log: - expect.toHaveText with timeout 10000ms - waiting for getByTestId('count') 14 × locator resolved to <p data-testid="count">0</p> - unexpected value "0" 68 | await expect(page.getByTestId('count')).toHaveText('0'); 69 | await page.getByTestId('increment').click(); > 70 | await expect(page.getByTestId('count')).toHaveText('1'); | ^ 71 | expect(messages.join('\n')).not.toContain('hydration-mismatch'); 72 | }); 73 | }); at /home/runner/work/waku/waku/e2e/ssr-basic.spec.ts:70:47

Check failure on line 70 in e2e/ssr-basic.spec.ts

View workflow job for this annotation

GitHub Actions / E2E on ubuntu-latest (Node 20.8.0) - (3/4)

[firefox] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error

1) [firefox] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error ─────────────────── Error: Timed out 10000ms waiting for expect(locator).toHaveText(expected) Locator: getByTestId('count') Expected string: "1" Received string: "0" Call log: - expect.toHaveText with timeout 10000ms - waiting for getByTestId('count') 14 × locator resolved to <p data-testid="count">0</p> - unexpected value "0" 68 | await expect(page.getByTestId('count')).toHaveText('0'); 69 | await page.getByTestId('increment').click(); > 70 | await expect(page.getByTestId('count')).toHaveText('1'); | ^ 71 | expect(messages.join('\n')).not.toContain('hydration-mismatch'); 72 | }); 73 | }); at /home/runner/work/waku/waku/e2e/ssr-basic.spec.ts:70:47

Check failure on line 70 in e2e/ssr-basic.spec.ts

View workflow job for this annotation

GitHub Actions / E2E on ubuntu-latest (Node 22.7.0) - (3/4)

[firefox] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error

1) [firefox] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error ─────────────────── Error: Timed out 10000ms waiting for expect(locator).toHaveText(expected) Locator: getByTestId('count') Expected string: "1" Received string: "0" Call log: - expect.toHaveText with timeout 10000ms - waiting for getByTestId('count') 14 × locator resolved to <p data-testid="count">0</p> - unexpected value "0" 68 | await expect(page.getByTestId('count')).toHaveText('0'); 69 | await page.getByTestId('increment').click(); > 70 | await expect(page.getByTestId('count')).toHaveText('1'); | ^ 71 | expect(messages.join('\n')).not.toContain('hydration-mismatch'); 72 | }); 73 | }); at /home/runner/work/waku/waku/e2e/ssr-basic.spec.ts:70:47

Check failure on line 70 in e2e/ssr-basic.spec.ts

View workflow job for this annotation

GitHub Actions / E2E on ubuntu-latest (Node 22.7.0) - (4/4)

[webkit] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error

1) [webkit] › ssr-basic.spec.ts:62:5 › ssr-basic: DEV › check hydration error ──────────────────── Error: Timed out 10000ms waiting for expect(locator).toHaveText(expected) Locator: getByTestId('count') Expected string: "1" Received string: "0" Call log: - expect.toHaveText with timeout 10000ms - waiting for getByTestId('count') 14 × locator resolved to <p data-testid="count">0</p> - unexpected value "0" 68 | await expect(page.getByTestId('count')).toHaveText('0'); 69 | await page.getByTestId('increment').click(); > 70 | await expect(page.getByTestId('count')).toHaveText('1'); | ^ 71 | expect(messages.join('\n')).not.toContain('hydration-mismatch'); 72 | }); 73 | }); at /home/runner/work/waku/waku/e2e/ssr-basic.spec.ts:70:47
expect(messages.join('\n')).not.toContain('hydration-mismatch');
});
});
}
8 changes: 0 additions & 8 deletions packages/waku/src/lib/plugins/vite-plugin-rsc-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ import type { Plugin } from 'vite';

import { SRC_MAIN } from '../constants.js';

// This should be consistent with the one in renderers/html.ts
const DEFAULT_HTML_HEAD = `
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content="Waku" />
`.trim();

export function rscIndexPlugin(opts: {
basePath: string;
srcDir: string;
Expand All @@ -19,7 +12,6 @@ export function rscIndexPlugin(opts: {
<!doctype html>
<html>
<head>
${DEFAULT_HTML_HEAD}
</head>
<body>
<script src="${opts.basePath}${opts.srcDir}/${SRC_MAIN}" async type="module"></script>
Expand Down
11 changes: 0 additions & 11 deletions packages/waku/src/lib/renderers/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,6 @@ import { renderRsc, renderRscElement, getExtractFormState } from './rsc.js';
// TODO move types somewhere
import type { HandlerContext } from '../middleware/types.js';

// This should be consistent with the one in vite-plugin-rsc-index.ts
const DEFAULT_HTML_HEAD = [
createElement('meta', { charSet: 'utf-8' }),
createElement('meta', {
name: 'viewport',
content: 'width=device-width, initial-scale=1',
}),
createElement('meta', { name: 'generator', content: 'Waku' }),
];

type Elements = Record<string, ReactNode>;

const fakeFetchCode = `
Expand Down Expand Up @@ -229,7 +219,6 @@ export async function renderHtml(
Omit<ComponentProps<typeof ServerRoot>, 'children'>
>,
{ elements: elementsPromise },
...DEFAULT_HTML_HEAD,
htmlNode as any,
),
{
Expand Down
24 changes: 22 additions & 2 deletions packages/waku/src/minimal/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ declare global {
}
}

const DEFAULT_HTML_HEAD = [
createElement('meta', { charSet: 'utf-8' }),
createElement('meta', {
name: 'viewport',
content: 'width=device-width, initial-scale=1',
}),
createElement('meta', { name: 'generator', content: 'Waku' }),
];

const BASE_PATH = `${import.meta.env?.WAKU_CONFIG_BASE_PATH}${
import.meta.env?.WAKU_CONFIG_RSC_BASE
}/`;
Expand Down Expand Up @@ -231,7 +240,12 @@ export const Root = ({
return createElement(
RefetchContext.Provider,
{ value: refetch },
createElement(ElementsContext.Provider, { value: elements }, children),
createElement(
ElementsContext.Provider,
{ value: elements },
...DEFAULT_HTML_HEAD,
children,
),
);
};

Expand Down Expand Up @@ -376,4 +390,10 @@ export const ServerRootInternal = ({
}: {
elements: Elements;
children: ReactNode;
}) => createElement(ElementsContext.Provider, { value: elements }, children);
}) =>
createElement(
ElementsContext.Provider,
{ value: elements },
...DEFAULT_HTML_HEAD,
children,
);

0 comments on commit 67775de

Please sign in to comment.