Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooltip cannot find ID of a React element rendered next to it #773

Open
octylFractal opened this issue Jan 10, 2018 · 42 comments
Open

Tooltip cannot find ID of a React element rendered next to it #773

octylFractal opened this issue Jan 10, 2018 · 42 comments

Comments

@octylFractal
Copy link

octylFractal commented Jan 10, 2018

Issue description

  • components: Tooltip
  • reactstrap version 5.0.0-alpha.4
  • import method umd, not full version
  • react version 16.2.0
  • bootstrap version 4.0.0-beta.3

What is happening?

Sometimes, when I render the attached code, I get this error: The target <target id> could not be identified in the dom, tip: check spelling. However, I can't get the codepen to reproduce this, but it consistently happens in my larger application base, which uses the same data flow. The bug occurs when the button in the codepen is pressed, but perhaps React functions differently in my case. I made a small change in the codepen, the bug is now reproduceable.

The full application is here: https://github.com/kenzierocks/OurTube/blob/master/client/js/navbar.tsx#L78

I can help set this up to run if needed, but it's still in the middle of initial development

What should be happening?

It should render my custom tooltip properly, with no errors.

Code

https://codepen.io/anon/pen/xpWJzN

@octylFractal
Copy link
Author

I've managed to reduce the problem even further: it seems that the problem is much simpler than I first thought. New codepen here has the issue.

@octylFractal octylFractal changed the title Tooltip cannot find ID of a React element rendered next to it if wrapped Tooltip cannot find ID of a React element rendered next to it Jan 11, 2018
@TheSharpieOne
Copy link
Member

The issue is that it is trying to find your target element before it is rendered to the DOM.
This happens when the tooltip is open when it is initialized.
Not too sure how to address this, but the workaround it is either toggle it after it initializes or provide a ref or function to get a ref/DOM node to target

@Cretezy
Copy link

Cretezy commented Jan 13, 2018

I was having this issue in my tests, which originally was:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

describe("App", () => {
	test("renders without crashing", () => {
		const div = document.createElement("div");
		ReactDOM.render(<App />, div);
	});
});

This would give the error discussed.

To solve it (at least for my tests), I had to add the div to the body, since the tooltip tries to query the document. This was the fix:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

describe("App", () => {
	test("renders without crashing", () => {
		const div = document.createElement("div");
		document.body.appendChild(div);
		ReactDOM.render(<App />, div);
	});
});

I hope this can bring any insights to what you are experiencing.

@k-funk
Copy link

k-funk commented Jan 24, 2018

I tried creating the element separately, to try to referencing the target directly (am I just misunderstanding? The docs don't have an example), but the error suggests that it's still looking for an element attached to the DOM at render-time: https://codepen.io/anon/pen/WdBBMW

@gpolyn
Copy link

gpolyn commented Jan 25, 2018

@k-funk I waited for a ref set on the target to !== undefined, then I was able to refer to the target DOM id.

EDIT ... but this approach doesn't seem to play well with my DOM router and back-buttoning history, perhaps due to a component lifecycle issue,

@FelixHenninger
Copy link

Hi everyone,

just wanted to drop in quickly, and say thanks for all of your work on reactstrap, and specifically on this issue! I would like to confirm that this still occurs with 5.0.0-beta; as for @Cretezy, in-browser production code works fine but CI builds via jsDom fail for me on the most basic tests; their workaround fixes things successfully.

I've been able to isolate the issue to UncontrolledTooltip components (I think) added to the page after the initial render, would be happy to investigate further if this is useful -- please let me know.

All the best, and thanks again to you all for this awesome library, it makes my work easier every day!

-Felix

@TheSharpieOne
Copy link
Member

TheSharpieOne commented Feb 15, 2018

The JSDOM issue is because you are not mounting your application/component to the document during testing. Simply mount your application/component to the document JSDOM creates and it will work.
This is needed because the reactstrap code looks in the document for the target and if the target is mounted outside of the document it cannot find it.
If you would like to investigate a better way for reactstrap to locate the target or something to help the tests not need to mount the application/component in the document, go for it.

@FelixHenninger
Copy link

The JSDOM issue is because you are not mounting your application/component to the document during testing. Simply mount your application/component to the document JSDOM creates and it will work.

Ah, I see, thanks! So that's how @Cretezy's workaround functions. That definitely makes sense. In that case, I think this part of the issue might be considered fixed.

Thanks a lot for your super-quick response, and again, thanks for your work on reactstrap!

@lucky7id
Copy link

lucky7id commented Feb 19, 2018

@TheSharpieOne Seeing this issue on UncontrolledTooltip at render time where the isOpen prop should be false (there is no way for the tips to be hovered before rendering).

FOLLOW UP EDIT So, in my case, it turned out that I had some dynamic complex id names like field.subprop-1. When I removed the . from the names the tips started working again. Hopefully this helps

@TheSharpieOne
Copy link
Member

Yeah, the target is a css selector, so the . indicates classname.
We may want to initially search for just id (via getElementById) and fallback to css selector.

@yidingalan
Copy link

Hey @TheSharpieOne , I tried this but it still throws the same error

it('test', () => {
  const div = document.createElement('div');
  document.body.appendChild(div);
  const wrapper = mount(<Component {...props} />);
});

Any idea why?

@TheSharpieOne
Copy link
Member

TheSharpieOne commented Mar 15, 2018

@yidingalan enzyme's mount takes a second parameter; options. Options has a property, attachTo which is used to tell it where to mount the component. It's not enough to add a div to the body, you have to then tell it to mount the component to that div.

it('test', () => {
  const div = document.createElement('div');
  document.body.appendChild(div);
  const wrapper = mount(<Component {...props} />, { attachTo: div });
});

More information about mount and it's options: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#mountnode-options--reactwrapper

@maxbureac
Copy link

maxbureac commented Mar 20, 2018

I have the same problem. I am using tooltip for a button. At first, everything seems ok but when I hover over the button the error is being thrown: Uncaught Error: The target 'tooltip-cftoce14f' could not be identified in the dom, tip: check spelling. Here is my code for the tooltip:

class FxTooltip extends React.Component {
    constructor(props) {
        super(props)

        this.toggle = this.toggle.bind(this)
        this.state = {
            tooltipOpen: false
        }
    }

    toggle() {
        this.setState({
            tooltipOpen: !this.state.tooltipOpen
        })
    }

    render() {
        const { body, placement = 'top', children } = this.props
        if (body) {
            const id = Math.random().toString(36).substr(2, 9)
            return <div>
                <div id={`tooltip-${id}`}>
                  <Button>Some button</Button>
                </div>
                <Tooltip
                    placement={placement}
                    toggle={this.toggle}
                    isOpen={this.state.tooltipOpen}
                    target={`tooltip-${id}`}
                    autohide={false}
                >
                    <span style={{ fontSize: 12, padding: '0 8px', lineHeight: '20px' }}>{body}</span>
                </Tooltip>
            </div>
        }
        return <div>
            {children}
        </div>
    }
}

Version details:

  • bootstrap@4.0.0
  • react@16.2.0
  • reactstrap@5.0.0-beta.2

Am I doing something wrong?

@TheSharpieOne
Copy link
Member

You are generating random ID values each render. So when you open the tooltip, it will create a new ID and when the tooltip tries to find an element with the new ID, it will not have been put into the DOM yet.
If you cache the IDs (created them once in your constructor and reference them from state) you should be able to avoid the issue.

@maxbureac
Copy link

@TheSharpieOne thanks a lot it worked! Completly missed this one 😄

@Thoum
Copy link

Thoum commented Mar 22, 2018

I have the same problem as @prodevelsol had 2 days ago but I can't figure it out... 😞 Can you help me find what is wrong in my code? It returns this error message :

The target 'rfmygrx07' could not be identified in the dom, tip: check spelling

Where 'rfmygrx07' is the random id

import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import { Row, Col, Tooltip } from 'reactstrap';
import s from './BadgeBenchmarkTooltip.css';
import Badge from '../../atoms/Badge/Badge';

class BadgeBenchmarkTooltip extends React.Component {
  constructor(props) {
    super(props);
    const generateID = Math.random().toString(36).substr(2, 9);
    this.toggle = this.toggle.bind(this);
    this.state = {
      tooltipOpen: false,
      id: generateID,
    };
  }
  toggle() {
    this.setState({
      tooltipOpen: !this.state.tooltipOpen,
    });
  }
  render() {
    return (
      <span id={this.state.id}>
        <Badge deltaYear={this.props.deltaYear} />
        <Tooltip
          className={s.tooltipAll}
          innerClassName={s.tooltip}
          placement="auto"
          isOpen={this.state.tooltipOpen}
          target={this.state.id}
          toggle={this.toggle}
        >
          <Row>
            <Col>
              <span className={s.tooltipTitle}>{this.props.title}</span>
            </Col>
          </Row>
          <Row>
            <Col xs={12}>
              <span className={s.tooltipSubTitle}>Year -1</span>
            </Col>
            <Col xs={12}>
              <span className={s.tooltipDate}>
                From June 2016 to November 2016
              </span>
            </Col>
            <Col xs={12}>
              <Badge deltaYear={12} />
            </Col>
            <Col xs={12}>
              <span className={s.tooltipNote}>{this.props.deltaYear}</span>
            </Col>
          </Row>
        </Tooltip>
      </span>
    );
  }
}

export default withStyles(s)(BadgeBenchmarkTooltip);

Note : As you can notice, my project integrates https://github.com/kriasoft/isomorphic-style-loader so classes are decorated, but not IDs, so i don't think it is the source of my problems (but I prefer to notice, just in case...)

Thanks ! 😃

@maxbureac
Copy link

@Thoum I think this can be caused by the fact that you put the id on the root level component so the component is being re-rendered and the id is not in the state. You can try to assign it to the Badge component instead.

<Badge deltaYear={this.props.deltaYear} id={this.state.id} />

is it the badge that has to be tooltip-ed?
Hope this is the issue 🙂

@Thoum
Copy link

Thoum commented Mar 22, 2018

@prodevelsol :
Sadly, this doesn't work... 😞 ...

  • I tried to replace the generated ID by a normal ID(simply written, not calculated like 'tooltipcard1') in my parent component and it works...
  • I tried to replace this 'normal' ID by the 'Math.random().toString(36).substr(2, 9);' and the same error occurs...
  • If i switch the methods of ID (between normal and generated commenting/decommenting the two ID lines) without manually refreshing page (auto refresh), it works, but as soon as i refresh page by myself, the error comes back...
const generateID = Math.random().toString(36).substr(2, 9);
    this.toggle = this.toggle.bind(this);
    this.state = {
      tooltipOpen: false,
      idtool: this.props.idTooltip,
      // idtool: `tooltip${generateID}`,
    };

Full code with modifications operated :

import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import { Row, Col, Tooltip, Button } from 'reactstrap';
import s from './BadgeBenchmarkTooltip.css';
import Badge from '../../atoms/Badge/Badge';

class BadgeBenchmarkTooltip extends React.Component {
  constructor(props) {
    super(props);
    const generateID = Math.random().toString(36).substr(2, 9);
    this.toggle = this.toggle.bind(this);
    this.state = {
      tooltipOpen: false,
      id: this.props.idTooltip,
      // id: `tooltip${generateID}`,
    };
  }
  toggle() {
    this.setState({
      tooltipOpen: !this.state.tooltipOpen,
    });
  }
  render() {
    return (
      <span className={s.tooltipBench}>
        <Badge id={this.state.id} deltaYear={this.props.deltaYear} />
        <Tooltip
          className={s.tooltipAll}
          innerClassName={s.tooltip}
          placement="auto"
          isOpen={this.state.tooltipOpen}
          target={this.state.id}
          hideArrow
          toggle={this.toggle}
        >
          <Row>
            <Col>
              <span className={s.tooltipTitle}>{this.props.title}</span>
            </Col>
          </Row>
          <Row className="mt-3">
            <Col xs={12}>
              <span className={s.tooltipSubTitle}>Year -1</span>
            </Col>
            <Col xs={12}>
              <span className={s.tooltipDate}>
                {this.props.datesForYear}
              </span>
            </Col>
            <Col xs={12} className="my-3">
              <Badge deltaYear={this.props.deltaYear} />
            </Col>
            <Col xs={12}>
              <span className={s.tooltipNote}>{this.props.noteYear}</span>
            </Col>
          </Row>
          <Row>
            <Col>
              <hr />
            </Col>
          </Row>
          <Row>
            <Col xs={12}>
              <span className={s.tooltipSubTitle}>Period -1</span>
            </Col>
            <Col xs={12}>
              <span className={s.tooltipDate}>
                {this.props.datesForPeriod}
              </span>
            </Col>
            <Col xs={12} className="my-3">
              <Badge deltaYear={this.props.deltaPeriod} />
            </Col>
            <Col xs={12}>
              <span className={s.tooltipNote}>{this.props.notePeriod}</span>
            </Col>
          </Row>
        </Tooltip>
      </span>
    );
  }
}

export default withStyles(s)(BadgeBenchmarkTooltip);

Thank you for your help ! 😃

@TheSharpieOne
Copy link
Member

I removed isomorphic-style-loader (and the classNames with references to the imported styles) and replaced Badge with reactstrap's badge (just to get it to render) and it works fine:
https://stackblitz.com/edit/reactstrap-v5beta-3-9cj9dj?file=Example.js

Can you inspect and confirm that the ID on the rendered element matches what the error is saying cannot be found? I see some stuff in that isomorphic-style-loader project which manipulates IDs, but I am not sure if that is the case.

@Thoum
Copy link

Thoum commented Mar 26, 2018

Yes i can confirm the ID is correct.
I made a console log of the state.id value and it's the same ID on the rendered element (no decoration added).
As soon as i reimport my tooltip component, the error shows :(
NB : no problem with 'static / non-generetad'. The problem is only when i generate the id randomly :/

@virgofx
Copy link
Collaborator

virgofx commented Mar 26, 2018

The issue is the way React handles parameters for IDs with strings and ints. You can't use an numeric starting value as the ID. (Hence why your Math.randoms() will break and your test values work) It must start with a string. We should add a note to this to the documentation portion for tooltips.

/cc @TheSharpieOne

@TheSharpieOne
Copy link
Member

The rules of HTML still apply, but I suppose we can add a note to explicitly point it out.
Typically when I generate an ID or something, I try to prefix it with the type of thing for which it was generated. Looking at his code, @Thoum is doing the same thing: tooltip${generateID} (at least at one point as it is commented out in the snippet posted).
The example I posted before had randomly generated ids and it worked fine 😕

moshthepitt added a commit to onaio/kaznet-frontend that referenced this issue Feb 8, 2019
Need to mount the component to the document that JSDOM creates
so that the dynamic Popover id can be found.

See: reactstrap/reactstrap#773 (comment)
moshthepitt added a commit to onaio/kaznet-frontend that referenced this issue Feb 8, 2019
Need to mount the component to the document that JSDOM creates
so that the dynamic Popover id can be found.

See: reactstrap/reactstrap#773 (comment)
@pmacmillan
Copy link

I'm having an issue that I think is similar to this.

In at least two places, the code checks for the target during render:

(note: commit ID used is just what the current master is, not related to issue)

This means that a component that renders the target and the tooltip (UncontrolledTooltip in my case) will only work on the subsequent render.

I hit this issue in the following scenario:

  • component renders
  • a state change (router) causes it to re-render with new parameters
  • leave mouse hovering over tooltip (the part that makes it difficult to test)
  • I was generating the id using an id unique to that row
  • on the render with the new parameters, the new element does not exist yet and cannot be found

I fixed my issue by adding a key={row.id} to the UncontrolledTooltip instance. This prevented react from re-using the component when the basis for the target id changed.

I wrote a crude test where I modified reactstrap and moved the getTarget calls into componentDidUpdate and it seemed to also fix the issue.

If there is interest, I can clean up my code a little and submit a PR.

@pmacmillan
Copy link

pmacmillan commented Mar 4, 2019

reproduction: https://codesandbox.io/s/144zyk1zjq

Modifying reactstrap to get the target in the appropriate lifecycle methods appears to fix this.

EDIT: a workaround/fix is to add key={counterId} to the UncontrolledTooltip to prevent react from re-using the element when the id changes.

@soywod
Copy link

soywod commented May 2, 2019

I had the same problem using react hooks. I solved it with a useEffect. My component is a Link component (a wrapper around <a>), I share the code in case someone needs:

import React, {Fragment, useEffect, useRef, useState} from 'react'
import classNames from 'classnames'
import Tooltip from 'reactstrap/lib/Tooltip'

export default function({className, to, tooltip, onClick, children}) {
  const linkRef = useRef()
  const [ready, setReady] = useState(false)
  const [open, setOpen] = useState(false)

  function handleClick(event) {
    event.preventDefault()
    onClick(event)
  }

  function toggle() {
    setOpen(!open)
  }

  useEffect(() => {
    if (linkRef.current) {
      setReady(true)
    }
  }, [linkRef.current])

  return (
    <Fragment>
      <a
        className={classNames(className)}
        ref={linkRef}
        target="_blank"
        rel="noopener noreferrer"
        href={to || '#'}
        {...(onClick ? {onClick: handleClick} : {})}
      >
        {children}
      </a>

      {tooltip && ready && (
        <Tooltip
          placement="bottom"
          isOpen={open}
          target={linkRef.current}
          toggle={toggle}
        >
          {tooltip}
        </Tooltip>
      )}
    </Fragment>
  )
}

@simeonborko
Copy link

@soywod , that's a very good solution! Using reference and to make sure that Tooltip will render after reference is applied.

@saikatguha
Copy link

I had the same problem and decided to wrap @soywod's solution in a component I can reference.
Here is how I use my component:

<TooltipContext>
    <TooltipTarget>
       {...arbitrary children here...}
    </TooltipTarget>
    <UncontrolledTooltip ...all props except target...>
       {... tooltip contents...}
    </UncontrolledTooltip>
</TooltipContext>

And here is the component that does the wiring up of target, and uses the Ref trick to work around the render order.

import React, { createContext, useRef, useContext, ReactNode, RefObject } from "react";
import { UncontrolledTooltip as BaseUncontrolledTooltip, UncontrolledTooltipProps } from "reactstrap";

const Context = createContext<RefObject<HTMLDivElement>>({ current: null });

export const TooltipContext = ({ children }: { children: ReactNode }) => {
  const ref = useRef<HTMLDivElement>(null);
  return (
    <Context.Provider value={ref}>
      {children}
    </Context.Provider>
  );
}

export const TooltipTarget = ({ children }: { children: ReactNode }) => {
  const ref = useContext(Context);
  return (
    <div ref={ref}>
      {children}
    </div>
  );
}

export const UncontrolledTooltip = ({ children, target, ...props }: Partial<UncontrolledTooltipProps>) => {
  const ref = useContext(Context);
  return !ref.current ? null : (
    <BaseUncontrolledTooltip target={ref.current} {...props}>
      {children}
    </BaseUncontrolledTooltip>
  );
}

@alamothe
Copy link

alamothe commented Oct 1, 2019

Key workaround suggested by @pmacmillan works for me.

The issue seems to happen if id is generated dynamically, and then the component is re-rendered (so id will change upon re-rendering).

@rmlevangelio
Copy link

rmlevangelio commented Jan 7, 2020

@TheSharpieOne What if you're using react-test-renderer instead of enzyme's mount? How would you address the issue? Actually I used @soywod 's solution for it. Putting a ref to the target and checking if it's ready, but I don't think it's a good solution because you need to modify the code just to make the test work.

This is only happening for me on snapshot testing and not in production.

Kornil added a commit to Kornil/design-react-kit that referenced this issue Jan 21, 2020
- reactstrap UncontrolledTooltip searches the document object for ids at first render, by adding invisible divs with the required ids th tests will pass and on render the tooltips will be correctly applied to the tests' correct buttons reactstrap/reactstrap#773 (comment)
- Update snaps
@AsadSaleh
Copy link

My current solution is by using generated ID, but I make sure that it only created once by using zero-dependencies useMemo, thus successfully generated a random ID that will not change in subsequent re-render. Hope this helps someone. Any suggestions if this solution has a downside are welcomed.

Code:

import React, { useState, useMemo } from "react";
import { Tooltip as BsTooltip } from "reactstrap";

const Tooltip = ({ description, children }: TooltipProps) => {
  const [tooltipOpen, setTooltipOpen] = useState(false);
  const toggle = () => setTooltipOpen(!tooltipOpen);
  const tooltipId = useMemo(
    () => "tooltip" + Math.floor(Math.random() * 1000),
    []
  );

  return (
    <>
      <BsTooltip
        placement="top"
        isOpen={tooltipOpen}
        autohide={false}
        target={tooltipId}
        toggle={toggle}
      >
        {description}
      </BsTooltip>
      <div id={tooltipId}>{children}</div>
    </>
  );
};

export default Tooltip;

interface TooltipProps {
  description?: string;
  children: React.ReactChild;
}

@soywod
Copy link

soywod commented Jan 24, 2020

@AsadSaleh I would use a UUID instead of generating a random number manually. Also, I think a useRef is more appropriate, since it's exactly what we need: a mutable reference of the tooltip.

@octylFractal
Copy link
Author

useRef is less efficient for holding a random ID because it needs to generate randomness every call, there's no lazy version like with memo. If you're suggesting holding a "ref", then it might be different.

@soywod
Copy link

soywod commented Jan 24, 2020

Yes sorry, I wasn't clear enough. The @AsadSaleh solution looks good, but I would use a UUID instead. Personally I would not use this method, I prefer to keep a tooltip ref as I shared in my previous comment.

@gpspake
Copy link

gpspake commented Mar 11, 2020

Worth mentioning that the same issue exists with popovers and continues to be a problem that we have to work around in hacky ways (for both tooltips and popovers). The useRef solution in this thread, which we've used until recently has its own issues and introduces console warnings - in addition to a bunch of confusing code to our components that exists entirely to accommodate this quirk in tests.

@kylekirkby
Copy link

Thanks all. FYI that this is still an issue.

@MatteoGioioso
Copy link

MatteoGioioso commented May 11, 2020

Same issue here and none of the solutions above have been working so far =(
We are also having: TypeError: Cannot read property 'removeEventListener' of undefined

@rmlevangelio
Copy link

rmlevangelio commented May 12, 2020

You can still use @TheSharpieOne solution for the meantime:

const btnRef = React.useRef(null);
const [ready, setReady] = React.useState(false);

  React.useEffect(() => {
    if (btnRef.current) {
      setReady(true);
    }
  }, [btnRef]);

<button
  ref={btnRef}
  type="button"
  id={id}
  aria-describedby="tooltip-id"
>
  Show tooltip
</button>
{ready && <Tooltip id="tooltip-id">some text</Tooltip>}

@MatteoGioioso
Copy link

MatteoGioioso commented May 12, 2020

Unfortunately we are using an ancient version of React without hooks 😞 , I will have to find a way to do without them.

@k-funk
Copy link

k-funk commented May 12, 2020

Unfortunately we are using an ancient version of React without hooks 😞 , I will have to find a way to do without them.

React hooks are just a shortcut for what react already does. It's worth reading through Using the State Hook and Using the Effect Hook.

Using classes, this would be something like:

export default class MyComponent extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      ready: false,
    };
    this.myRef = React.createRef();
  }

  componentDidMount() {
    this.setReady();
  }
  
  componentDidUpdate() {
    this.setReady();
  }
  
  setReady() {
    if (this.myRef.current) {
      this.setState({
        ready: true,
      });
    }
  }


  render() {
    const { ready } = this.state;
    return (
      <>
        <button
          ref={this.myRef}
          type="button"
          id={id}
          aria-describedby="tooltip-id"
        >
          Show tooltip
        </button>
        {ready && <Tooltip id="tooltip-id">some text</Tooltip>}
      </>
    );
  }
}

@MatteoGioioso
Copy link

MatteoGioioso commented May 12, 2020

@k-funk hey, thanks a bunch. I needed to make a little fix to the setReady method:

 setReady() {
    if (this.myRef.current && this.state.ready === false) {
      this.setState({
        ready: true,
      });
    }
  }

@MatteoGioioso
Copy link

Dammit some of my tests are still failing, I think for the time being I will have to remove it.

@trevorbye
Copy link

trevorbye commented Aug 19, 2020

@yidingalan enzyme's mount takes a second parameter; options. Options has a property, attachTo which is used to tell it where to mount the component. It's not enough to add a div to the body, you have to then tell it to mount the component to that div.

it('test', () => {
  const div = document.createElement('div');
  document.body.appendChild(div);
  const wrapper = mount(<Component {...props} />, { attachTo: div });
});

More information about mount and it's options: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#mountnode-options--reactwrapper

This worked perfectly when using mount(). I had to make a couple adjustments that hopefully might help if you're stuck; first of which is setting the actual id with setAttribute(), and you set this to the tooltip id that react can't locate. Second, I had multiple tooltips in a component, so I had to nest one inside another, append the outer to the DOM, but reference the inner one when attaching to mount as follows:

it('test', () => {
  const tooltipDiv = document.createElement('div');
  const innerTooltipDiv = document.createElement('div');

  tooltipDiv.setAttribute("id", "some-tooltip-id");
  innerTooltipDiv.setAttribute("id", "another-tooltip-id");

  tooltipDiv.appendChild(innerTooltipDiv);
  document.body.appendChild(tooltipDiv);
  const wrapper = mount(<Component {...props} />, { attachTo: innerTooltipDiv});
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests