diff --git a/CHANGELOG.md b/CHANGELOG.md index 9337890..44447e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Forward the `ref` to all components ([#1116](https://github.com/tailwindlabs/headlessui/pull/1116)) - Ensure links are triggered inside `Popover Panel` components ([#1153](https://github.com/tailwindlabs/headlessui/pull/1153)) +- Improve SSR for `Tab` component ([#1155](https://github.com/tailwindlabs/headlessui/pull/1155)) ## [Unreleased - @headlessui/vue] diff --git a/packages/@headlessui-react/src/components/tabs/tabs.tsx b/packages/@headlessui-react/src/components/tabs/tabs.tsx index b6d6079..4bdd1d3 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.tsx @@ -26,6 +26,7 @@ import { focusIn, Focus } from '../../utils/focus-management' import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' import { useSyncRefs } from '../../hooks/use-sync-refs' import { useResolveButtonType } from '../../hooks/use-resolve-button-type' +import { useLatestValue } from '../../hooks/use-latest-value' interface StateDefinition { selectedIndex: number | null @@ -103,6 +104,9 @@ let TabsContext = createContext< >(null) TabsContext.displayName = 'TabsContext' +let TabsSSRContext = createContext | null>(null) +TabsSSRContext.displayName = 'TabsSSRContext' + function useTabsContext(component: string) { let context = useContext(TabsContext) if (context === null) { @@ -147,14 +151,14 @@ let Tabs = forwardRefWithAs(function Tabs ({ selectedIndex: state.selectedIndex }), [state.selectedIndex]) - let onChangeRef = useRef<(index: number) => void>(() => {}) + let onChangeRef = useLatestValue(onChange || (() => {})) useEffect(() => { dispatch({ type: ActionTypes.SetOrientation, orientation }) @@ -164,12 +168,6 @@ let Tabs = forwardRefWithAs(function Tabs { - if (typeof onChange === 'function') { - onChangeRef.current = onChange - } - }, [onChange]) - useEffect(() => { if (state.tabs.length <= 0) return if (selectedIndex === null && state.selectedIndex !== null) return @@ -225,15 +223,19 @@ let Tabs = forwardRefWithAs(function Tabs - {render({ - props: { ref: tabsRef, ...passThroughProps }, - slot, - defaultTag: DEFAULT_TABS_TAG, - name: 'Tabs', - })} - + + + {render({ + props: { ref: tabsRef, ...passThroughProps }, + slot, + defaultTag: DEFAULT_TABS_TAG, + name: 'Tabs', + })} + + ) }) @@ -414,6 +416,11 @@ let Panel = forwardRefWithAs(function Panel ) { let [{ selectedIndex, tabs, panels }, { dispatch }] = useTabsContext('Tab.Panel') + let SSRContext = useContext(TabsSSRContext) + + if (SSRContext !== null && selectedIndex === null) { + selectedIndex = 0 // Should normally not happen, but in case the selectedIndex is null, we can default to 0. + } let id = `headlessui-tabs-panel-${useId()}` let internalPanelRef = useRef(null) @@ -428,7 +435,8 @@ let Panel = forwardRefWithAs(function Panel ({ selected }), [selected]) let propsWeControl = { diff --git a/packages/playground-react/pages/tabs/tabs-with-pure-tailwind.tsx b/packages/playground-react/pages/tabs/tabs-with-pure-tailwind.tsx index c1cc475..2323c2b 100644 --- a/packages/playground-react/pages/tabs/tabs-with-pure-tailwind.tsx +++ b/packages/playground-react/pages/tabs/tabs-with-pure-tailwind.tsx @@ -40,7 +40,12 @@ export default function Home() { - + {tabs.map((tab, tabIdx) => (