diff --git a/.changeset/flexrender-empty-string.md b/.changeset/flexrender-empty-string.md new file mode 100644 index 0000000000..08f4d509d2 --- /dev/null +++ b/.changeset/flexrender-empty-string.md @@ -0,0 +1,5 @@ +--- +'@tanstack/vue-table': patch +--- + +Render nothing instead of an empty string in `flexRender`, so a cell whose value is `''` no longer causes a hydration mismatch under SSR. diff --git a/packages/vue-table/src/FlexRender.ts b/packages/vue-table/src/FlexRender.ts index b257a2dd4a..6a10376a1d 100644 --- a/packages/vue-table/src/FlexRender.ts +++ b/packages/vue-table/src/FlexRender.ts @@ -32,6 +32,10 @@ export function flexRender(render: any, props: any): any { return render } + if (render === '') { + return null + } + if (typeof render === 'function') { const rendered = render(props) @@ -39,6 +43,10 @@ export function flexRender(render: any, props: any): any { return rendered } + if (rendered === '') { + return null + } + if (isVNode(rendered)) { return rendered } diff --git a/packages/vue-table/tests/unit/flexRenderHydration.test.ts b/packages/vue-table/tests/unit/flexRenderHydration.test.ts new file mode 100644 index 0000000000..ba3f4939af --- /dev/null +++ b/packages/vue-table/tests/unit/flexRenderHydration.test.ts @@ -0,0 +1,49 @@ +// @vitest-environment jsdom + +import { afterEach, describe, expect, test, vi } from 'vitest' +import { createSSRApp, defineComponent, h } from 'vue' +import { renderToString } from 'vue/server-renderer' +import { FlexRender, flexRender } from '../../src/FlexRender' + +afterEach(() => { + vi.restoreAllMocks() +}) + +function makeApp(cellValue: string) { + return defineComponent({ + setup() { + return () => + h('table', [ + h('tbody', [ + h('tr', [h('td', [h(FlexRender, { render: cellValue })])]), + ]), + ]) + }, + }) +} + +describe('FlexRender hydration', () => { + test('renders nothing for an empty string', () => { + expect(flexRender('', {})).toBeNull() + expect(flexRender(() => '', {})).toBeNull() + }) + + test('hydrates an empty string cell without a mismatch', async () => { + const App = makeApp('') + const html = await renderToString(createSSRApp(App)) + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const error = vi.spyOn(console, 'error').mockImplementation(() => {}) + const container = document.createElement('div') + + container.innerHTML = html + createSSRApp(App).mount(container, true) + + const messages = [...warn.mock.calls, ...error.mock.calls].map((args) => + String(args[0]), + ) + + expect(messages.filter((message) => message.includes('Hydration'))).toEqual( + [], + ) + }) +})