How to use a shadcn <Button> inside shadcn <TooltipProvider> ?
Answered
European imported fire ant posted this in #help-forum
European imported fire antOP
Hello, I am trying to create a tooltip on a Button. Both are taken from shadcn. The way I have it set up is the following:
This is the button code in a component called "ClientChatLogButton". It displays like an icon (<MessagesSquare /> from Lucide)
This is the Tooltip.
When the page loads, I get hydration errors but everything works. Below are the errors. From my understanding it doesnt like the fact that there is a button inside the TooltipProvider. When I comment out the <ClientChatLogButton /> component and replace it with plain text it wont complain. Any Ideas on how I can implement this functionality ?
<Button
variant='ghost'
onClick={() => { router.push("/") }}
>
<MessagesSquare />
</Button>
This is the button code in a component called "ClientChatLogButton". It displays like an icon (<MessagesSquare /> from Lucide)
This is the Tooltip.
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<ClientChatLogButton />
</TooltipTrigger>
<TooltipContent>
<p>View saved</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
When the page loads, I get hydration errors but everything works. Below are the errors. From my understanding it doesnt like the fact that there is a button inside the TooltipProvider. When I comment out the <ClientChatLogButton /> component and replace it with plain text it wont complain. Any Ideas on how I can implement this functionality ?
Answered by European imported fire ant
Yep, that was the issue, I changed my custom button like this and it worked
"use client"
import { MessagesSquare } from "lucide-react"
import { useRouter } from "next/navigation"
function ClientChatLogButton() {
const router = useRouter()
return (
<div
className='
onClick={() => { router.push("/chats") }}
>
<MessagesSquare />
</div>
)
}
export default ClientChatLogButton
48 Replies
Have you tried adding the
suppressHydrationWarning
attribute to the tooltip<TooltipProvider>
<Tooltip suppressHydrationWarning > # See my change here
<TooltipTrigger>
<ClientChatLogButton />
</TooltipTrigger>
<TooltipContent>
<p>View saved</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
This is [solution #3 from hydration errors](https://nextjs.org/docs/messages/react-hydration-error)
European imported fire antOP
Im not sure I understand why thats an error so I didnt want to just suppress it
So when react renders things on the server. It does a check to make sure what your server sees, is the same thing the client sees.
European imported fire antOP
And why wouldnt it in my case ?
the button is a client component btw
I dont know the source code for the button, but sometimes when components are generated, the CSS classes have procedrually generated classes
So, if you use a math.random function for example
Your server would get 5 and your client would get 2
European imported fire antOP
"use client"
import { Button } from "../ui/button"
import { MessagesSquare } from "lucide-react"
import { useRouter } from "next/navigation"
function ClientChatLogButton() {
const router = useRouter()
return (
<Button
variant='ghost'
onClick={() => { router.push("/chats") }}
>
<MessagesSquare />
</Button>
)
}
export default ClientChatLogButton
this is the button
isnt that serious though ? Should I suppress it ?
is the button file, client as well.
You can, if its just a client side button.
European imported fire antOP
Button comes from shadcn
in this file
import { Button } from "../ui/button"
European imported fire antOP
ctrl + click the link in your IDE
does the file in the root of that say 'use client;?
European imported fire antOP
its not. I dont think shadcn gives you client components
Ah I see now
you can make it client
European imported fire antOP
its a server thing inside a client component
So, answer me this. Does the button serve any server functionality at all?
European imported fire antOP
import { Button } from "../ui/button"
This is used as a base to make my own buttons. Some of them are client, others are server side
Even though I made it client I still get the same error
Could you open the inspect element in your browser
It tells you, what extactly is out of sync.
European imported fire antOP
Also
European imported fire antOP
I think the problem is that the icon from Lucide is a button, and its already in a button
European imported fire antOP
Yep, that was the issue, I changed my custom button like this and it worked
"use client"
import { MessagesSquare } from "lucide-react"
import { useRouter } from "next/navigation"
function ClientChatLogButton() {
const router = useRouter()
return (
<div
className='
onClick={() => { router.push("/chats") }}
>
<MessagesSquare />
</div>
)
}
export default ClientChatLogButton
Answer
Large Münsterländer
bro just set the "asChild" prop on <TooltipTrigger> lmfaoo
European imported fire antOP
what does this do ?
Large Münsterländer
It IS what you are searching for
European imported fire antOP
Yea im just asking what asChild does
It merges the parent's components props down with it's child
both Tooltip and Button render a <button> tag, if you add aschild to the tooltip component it'll merge the Tooltip event listeners to the Button component and render both as a single tag
works in server components and it's a really nice composition practice although the radix team themselves said it has drawbacks
It requires that the component only has a single child though. e.g. this wont work
<TooltipTrigger asChild>
<Button />
<span />
</TooltipTrigger>
but this will if you install the @radix-ui/react-slot lib (which is the lib they use internally to create the asChild prop)
<TooltipTrigger asChild>
<Slottable>
<Button />
</Slottable>
<span />
</TooltipTrigger>
Large Münsterländer
Take it or leave it
European imported fire antOP
Not very helpful today are we
European imported fire antOP
So would you advise I go with the asChild or is my solution ok too ?
from what I can see you made a div interactive, that's not an ok solution not even in regular html
Do this
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild><ClientChatLogButton /></TooltipTrigger>
</Tooltip>
<TooltipContent>
<p>View saved</p>
</TooltipContent>
<TooltipProvider/>
make sure to use React.forwardRef on your ClientChatLogButton comp
European imported fire antOP
So I use asChild on tooltiptrigger, then in my component I receive that as a forwardref and render it as the button I already have in my component right ?