Skip to content

Commit 5ef5cf9

Browse files
authored
Improve SSR for Tabs in Vue (#2068)
* improve SSR for Tabs in Vue * update changelog
1 parent 2f0dc8c commit 5ef5cf9

File tree

3 files changed

+104
-4
lines changed

3 files changed

+104
-4
lines changed

packages/@headlessui-vue/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Fix crash when using `multiple` mode without `value` prop (uncontrolled) for `Listbox` and `Combobox` components ([#2058](https://github.com/tailwindlabs/headlessui/pull/2058))
1717
- Allow passing in your own `id` prop ([#2060](https://github.com/tailwindlabs/headlessui/pull/2060))
1818
- Add `null` as a valid type for Listbox and Combobox in Vue ([#2064](https://github.com/tailwindlabs/headlessui/pull/2064), [#2067](https://github.com/tailwindlabs/headlessui/pull/2067))
19+
- Improve SSR for Tabs in Vue ([#2068](https://github.com/tailwindlabs/headlessui/pull/2068))
1920

2021
## [1.7.4] - 2022-11-03
2122

packages/@headlessui-vue/src/components/tabs/tabs.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { nextTick, ref } from 'vue'
1+
import { createSSRApp, nextTick, ref } from 'vue'
2+
import { renderToString } from 'vue/server-renderer'
23
import { createRenderTemplate, render } from '../../test-utils/vue-testing-library'
34
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from './tabs'
45
import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs'
@@ -554,6 +555,60 @@ describe('Rendering', () => {
554555
assertTabs({ active: 2 })
555556
})
556557
})
558+
559+
describe('SSR', () => {
560+
it('should be possible to server side render the first Tab and Panel', async () => {
561+
let app = createSSRApp({
562+
components: { TabGroup, TabList, Tab, TabPanels, TabPanel },
563+
template: html`
564+
<TabGroup>
565+
<TabList>
566+
<Tab>Tab 1</Tab>
567+
<Tab>Tab 2</Tab>
568+
<Tab>Tab 3</Tab>
569+
</TabList>
570+
571+
<TabPanels>
572+
<TabPanel>Content 1</TabPanel>
573+
<TabPanel>Content 2</TabPanel>
574+
<TabPanel>Content 3</TabPanel>
575+
</TabPanels>
576+
</TabGroup>
577+
`,
578+
})
579+
580+
let contents = await renderToString(app)
581+
expect(contents).toContain(`Content 1`)
582+
expect(contents).not.toContain(`Content 2`)
583+
expect(contents).not.toContain(`Content 3`)
584+
})
585+
586+
it('should be possible to server side render the defaultIndex Tab and Panel', async () => {
587+
let app = createSSRApp({
588+
components: { TabGroup, TabList, Tab, TabPanels, TabPanel },
589+
template: html`
590+
<TabGroup :defaultIndex="1">
591+
<TabList>
592+
<Tab>Tab 1</Tab>
593+
<Tab>Tab 2</Tab>
594+
<Tab>Tab 3</Tab>
595+
</TabList>
596+
597+
<TabPanels>
598+
<TabPanel>Content 1</TabPanel>
599+
<TabPanel>Content 2</TabPanel>
600+
<TabPanel>Content 3</TabPanel>
601+
</TabPanels>
602+
</TabGroup>
603+
`,
604+
})
605+
606+
let contents = await renderToString(app)
607+
expect(contents).not.toContain(`Content 1`)
608+
expect(contents).toContain(`Content 2`)
609+
expect(contents).not.toContain(`Content 3`)
610+
})
611+
})
557612
})
558613

559614
describe('`selectedIndex`', () => {

packages/@headlessui-vue/src/components/tabs/tabs.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ function useTabsContext(component: string) {
5858
return context
5959
}
6060

61+
let TabsSSRContext = Symbol('TabsSSRContext') as InjectionKey<
62+
Ref<{ tabs: string[]; panels: string[] } | null>
63+
>
64+
6165
// ---
6266

6367
export let TabGroup = defineComponent({
@@ -84,7 +88,7 @@ export let TabGroup = defineComponent({
8488
)
8589

8690
let api = {
87-
selectedIndex,
91+
selectedIndex: computed(() => selectedIndex.value ?? props.defaultIndex ?? null),
8892
orientation: computed(() => (props.vertical ? 'vertical' : 'horizontal')),
8993
activation: computed(() => (props.manual ? 'manual' : 'auto')),
9094
tabs,
@@ -116,6 +120,16 @@ export let TabGroup = defineComponent({
116120

117121
provide(TabsContext, api)
118122

123+
let SSRCounter = ref({ tabs: [], panels: [] })
124+
let mounted = ref(false)
125+
onMounted(() => {
126+
mounted.value = true
127+
})
128+
provide(
129+
TabsSSRContext,
130+
computed(() => (mounted.value ? null : SSRCounter.value))
131+
)
132+
119133
watchEffect(() => {
120134
if (api.tabs.value.length <= 0) return
121135
if (props.selectedIndex === null && selectedIndex.value !== null) return
@@ -231,7 +245,22 @@ export let Tab = defineComponent({
231245
onMounted(() => api.registerTab(internalTabRef))
232246
onUnmounted(() => api.unregisterTab(internalTabRef))
233247

234-
let myIndex = computed(() => api.tabs.value.indexOf(internalTabRef))
248+
let SSRContext = inject(TabsSSRContext)!
249+
let mySSRIndex = computed(() => {
250+
if (SSRContext.value) {
251+
let mySSRIndex = SSRContext.value.tabs.indexOf(props.id)
252+
if (mySSRIndex === -1) return SSRContext.value.tabs.push(props.id) - 1
253+
return mySSRIndex
254+
}
255+
256+
return -1
257+
})
258+
259+
let myIndex = computed(() => {
260+
let myIndex = api.tabs.value.indexOf(internalTabRef)
261+
if (myIndex === -1) return mySSRIndex.value
262+
return myIndex
263+
})
235264
let selected = computed(() => myIndex.value === api.selectedIndex.value)
236265

237266
function activateUsing(cb: () => FocusResult) {
@@ -391,7 +420,22 @@ export let TabPanel = defineComponent({
391420
onMounted(() => api.registerPanel(internalPanelRef))
392421
onUnmounted(() => api.unregisterPanel(internalPanelRef))
393422

394-
let myIndex = computed(() => api.panels.value.indexOf(internalPanelRef))
423+
let SSRContext = inject(TabsSSRContext)!
424+
let mySSRIndex = computed(() => {
425+
if (SSRContext.value) {
426+
let mySSRIndex = SSRContext.value.panels.indexOf(props.id)
427+
if (mySSRIndex === -1) return SSRContext.value.panels.push(props.id) - 1
428+
return mySSRIndex
429+
}
430+
431+
return -1
432+
})
433+
434+
let myIndex = computed(() => {
435+
let myIndex = api.panels.value.indexOf(internalPanelRef)
436+
if (myIndex === -1) return mySSRIndex.value
437+
return myIndex
438+
})
395439
let selected = computed(() => myIndex.value === api.selectedIndex.value)
396440

397441
return () => {

0 commit comments

Comments
 (0)