How to use a shadcn <Button> inside shadcn <TooltipProvider> ?
Answered
Ichneumonid wasp posted this in #help-forum
Ichneumonid waspOP
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 Ichneumonid wasp
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 ClientChatLogButton48 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)
Ichneumonid waspOP
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.
Ichneumonid waspOP
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
Ichneumonid waspOP
"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 ClientChatLogButtonthis is the button
@Smultar.json Your server would get 5 and your client would get 2
Ichneumonid waspOP
isnt that serious though ? Should I suppress it ?
is the button file, client as well.
@Ichneumonid wasp isnt that serious though ? Should I suppress it ?
You can, if its just a client side button.
Ichneumonid waspOP
Button comes from shadcn
in this file
import { Button } from "../ui/button"Ichneumonid waspOP
ctrl + click the link in your IDE
does the file in the root of that say 'use client;?
Ichneumonid waspOP
its not. I dont think shadcn gives you client components
Ah I see now
you can make it client
Ichneumonid waspOP
its a server thing inside a client component
So, answer me this. Does the button serve any server functionality at all?
Ichneumonid waspOP
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.
Ichneumonid waspOP
Also
Ichneumonid waspOP
I think the problem is that the icon from Lucide is a button, and its already in a button
Ichneumonid waspOP
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 ClientChatLogButtonAnswer
Large Münsterländer
bro just set the "asChild" prop on <TooltipTrigger> lmfaoo
@Large Münsterländer bro just set the "asChild" prop on <TooltipTrigger> lmfaoo
Ichneumonid waspOP
what does this do ?
Large Münsterländer
It IS what you are searching for
Ichneumonid waspOP
Yea im just asking what asChild does
@Ichneumonid wasp 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>@Ichneumonid wasp Yea im just asking what asChild does
Large Münsterländer
Take it or leave it
Ichneumonid waspOP
Not very helpful today are we
@rortan It requires that the component only has a single child though. e.g. this wont work
<TooltipTrigger asChild>
<Button />
<span />
</TooltipTrigger>
Ichneumonid waspOP
So would you advise I go with the asChild or is my solution ok too ?
@Ichneumonid wasp 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
@rortan Do this
tsx
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild><ClientChatLogButton /></TooltipTrigger>
</Tooltip>
<TooltipContent>
<p>View saved</p>
</TooltipContent>
<TooltipProvider/>
Ichneumonid waspOP
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 ?