Reach-ui: React components as Tooltip children?

Created on 10 May 2019  Â·  15Comments  Â·  Source: reach/reach-ui

Using @reach/tooltip, and getting a lot of errors about getBoundingClientRect being undefined whenever I nest a React component within the tooltip:

<Tooltip label='Hello!'>
  <Box>foo</Box>
</Tooltip>

Screen Shot 2019-05-10 at 1 03 34 PM

If I pass just a regular HTML element, it works just fine. But it seems like components should be supported as children, no?

Most helpful comment

I just got here after some time of "Why isn't the tooltip showing at my button?!". Turns out it doesn't work on function components at all.

I think the docs should totally mention this, as it's pretty severe.

All 15 comments

I ran into this today. The underlying component must be able to the ref that Tooltip is cloning onto the child component. I ended up just modifying my functional component to use forwardRef.

If not, you might have to go a low-level so that you can use the useTooltip hook and pass the ref using another convention such as innerRef that has become common.

For more info on refs check the React docs here: https://reactjs.org/docs/refs-and-the-dom.html#adding-a-ref-to-a-class-component

@ericchernuka Hmm, this makes sense but I am not sure exactly what I need to do to solve this.

Here's the code for my tooltip component:

const Tooltip = (props: TypeProps) => {
  const {
    label,
    position = "bottom",
    ariaLabel,
    children,
    zIndex = 1,
    qa,
    ...rest
  } = props;

  const [trigger, tooltip] = useTooltip({ DEBUG_STYLE: false });

  return (
    <React.Fragment>
      {React.cloneElement(children, trigger)}

      <ReachTooltip
        {...tooltip}
        label={label}
        ariaLabel={ariaLabel || label}
        position={positions[position]}
        zIndex={zIndex}
        {...rest}
      />
    </React.Fragment>
  );
};

I am using the hook, but I am not sure what ref I am supposed to pass where.

The trigger from useTooltip owns the ref. It can either be passed in my the ref key or you can let useTooltip initialize one for you. It then gets cloned into the child component. So the child component, <Box /> in your case needs to be able to forward the ref to a DOM node.

Ah, I see. So, no matter what I do, this will require source changes to the child component (i.e. this will never work with a component that doesn't forward refs?)

Pretty sure that’s correct. My implementation was broken today until I added ref forwarding.

Gotcha. Thanks for the help! This is kind of a bummer because we can't control what the consumer of the component passes in, and if they don't pass a component that forwards refs, it will break in a really weird way.

I ended up solving this by doing something like this:

return (
    <React.Fragment>
      <Box {...trigger}>
        {children}
      </Box>

      <TooltipContainer
        {...tooltip}
        label={label}
        ariaLabel={ariaLabel || label}
        position={positions[position]}
        zIndex={zIndex}
        {...rest}
      />
    </React.Fragment>
  );

Basically wrapping the children of the tooltip in a container that forwards refs (I updated Box to forward refs). Don't love wrapping the children in an unnecessary element, but it works!

This is a major shortcoming, imo. While building a design system, I cannot guarantee that the children will be components that forward refs.

const Box = forwardRef((props, ref) => {
  return <div ref={ref}/>
})

Tooltip needs a ref so it can manage focus, so you need to forward it.

If you don’t like that (seems weird, but okay) drop down to the useTooltip hook and spread the return values wherever you need it.

But there’s no need to start wrapping for one off situations. Forward Ref is what you want.

@ryanflorence is there anything in the docs stating that forwardRefs needs to be used? I see a lot of people having the same issue.

@ryanflorence I've tried doing what you suggested, but it doesn't seem to work on the second example with <Span />

https://codesandbox.io/s/naughty-sun-7q7bo

@raunofreiberg Since the tooltip clones its children, you have to ensure you pass all the props onto the underlying component. So in your example, you need to spread the props from forwardRef onto the span so that all the on* handlers still work. I've forked your example with a working one found here: https://codesandbox.io/s/optimistic-benz-ldg49?fontsize=14

I just got here after some time of "Why isn't the tooltip showing at my button?!". Turns out it doesn't work on function components at all.

I think the docs should totally mention this, as it's pretty severe.

@brookback Not sure why you're saying it doesn't work, but you may need to wrap your functional component in forwardRef and pass that ref to the child component so that the tooltip has something to attach to.

As an aside, I can make a PR that indicates that the underlying component requires a forwardRef if people are ok with that?

@ericchernuka Sorry – it worked after I found my way here and read instructions on forwardRef :) Just saying the docs could need some love around this.

This shows up in the console at Codesandbox (although not for me locally 🤔):

image

So it looks like it's taken care of already.

Was this page helpful?
0 / 5 - 0 ratings