headlessui
headlessui copied to clipboard
Tabs panels render inconsistency in SSR context (React)
What package within Headless UI are you using?
@headlessui/react
What version of that package are you using?
v1.6.2
What browser are you using?
Chrome
What version of React are you using?
v17.0.2
Reproduction URL
Repo: https://github.com/ruisaraiva19/remix-demo-headlessui-tabs-issue
Demo: https://remix-demo-headlessui-tabs-issue.pages.dev/
Describe your issue
Starting on version v1.6.1, the tabs panels rendering on SSR is inconsistent. There are two scenarios currently happening:
- When the
Tab.GroupdefaultIndexis set to0, it renders all tabs panels on SSR where it should only be rendering the first one. After hydration, it renders only the first tab panel which is expected. Demo - When the
Tab.GroupdefaultIndexis set to a number other than0, it does not render any tab panel on SSR. After hydration, it renders the correct tab panel. Demo
Basically, the hydration part is working as expected, but the SSR part is rendering either everything or nothing 😅 .
Version 1.6.0 renders the correct tab panel on SSR but has the hydration issue that was fixed on version 1.6.1.
Hey! Thank you for your bug report! Much appreciated! 🙏
Will have to dig deeper into what Remix is doing exactly. On Next.js this works as expected: https://headlessui-react-np7xchvnw-tailwindlabs.vercel.app/tabs/tabs-with-pure-tailwind
When I bump react & react-dom to version 18 then it works fine for me. Is updating your React version an option for you?
@RobinMalfait unfortunately no, I can't update React to version 18 right now.
@RobinMalfait the example that you are sharing, is that react 18?
I've experiencing the same as @ruisaraiva19 on a nextjs project, with react 17.
@joggienl yes, the issue happens when using React 17
For what it is worth, it seems like this behaviour is manifesting itself only after production build, not in dev mode.
What I tried is running nextjs with npm run dev and npm run build && npm run start. If I throttle my internet via the devtools, the issue gets more vissible.
I've spend a couple minutes to setup a simple and probably "stupid" stackblitz example: https://stackblitz.com/edit/nextjs-wxjhcy?file=pages/index.js
In order to "see" it, go to the terminal (in stackblitz). That should be open by default. Stop the current process (it is npx run dev). Use this command to start a production build:
rm -rf .next && NODE_ENV=production npx next build && NODE_ENV=production npx next start
It should show something similar as the first Tab example from https://headlessui.dev/react/tabs, only i've added that only a couple of times ;-)
If you refresh the example you should see a "stutter" (it goes very fast on this example, could not make it slower at this point). What could help is using devtools to set the network speed to "slow 3g". Note that at this speed you won't be able to build anything on stackblitz but you can enable that after the run start command.
On line 62 of index.js it is possible to update the value of defaultIndex. That showcases the similar behaviour that @ruisaraiva19 also showed.
A little update, it seems to happen in Chrome, when running a production build. In Safari it is working as expected.
Also seeing this problem in next.js. Pre-hydration all tabs are rendered. You notice the layout shift on first load of the page. Only in production build.
This is also super obvious if you disable javascript. Reproducible on the sample code:

Even we are also facing this same issue in our production website. We are running on React 17 and using CRA.
Hey, we've implemented a fix for this in React 17. There is a caveat related to Strict Mode however:
There is a limitation of React itself before v18 and how it implements Strict Mode. This is not something we can work around. I suggest you either upgrade to React 18 where this isn't a problem at all, disable Strict Mode in development, or just ignore the hydration errors. The core if the problem is how we've had to implement tab detection during SSR and Strict Mode's double rendering. React 18 doesn't have this problem because we use React 18's useId hook which uses internal mechanisms to bypass issues caused by double rendering.
Hopefully this will get rid of any issues you are seeing.
This will be available in the next tagged release. In the meantime you can test it using our insiders build (which will take a few minutes to publish): npm install @headlessui/react@insiders