Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/flexrender-empty-string.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions packages/vue-table/src/FlexRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,21 @@ export function flexRender(render: any, props: any): any {
return render
}

if (render === '') {
return null
}

if (typeof render === 'function') {
const rendered = render(props)

if (rendered === null || rendered === undefined) {
return rendered
}

if (rendered === '') {
return null
}

if (isVNode(rendered)) {
return rendered
}
Expand Down
49 changes: 49 additions & 0 deletions packages/vue-table/tests/unit/flexRenderHydration.test.ts
Original file line number Diff line number Diff line change
@@ -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(
[],
)
})
})