3

In my app, I'm trying to use Popper to create a tooltip over every element in the app. (Usually, I would only show a single tooltip, but for a presentation I want to show more than one).

I wrote this utility Component to attach tooltip directly to ref.

It works pretty well, but when I try to use it inside an [].map() like regular react components, I lose all my positioning: https://bit.dev/bit/base/atoms/ref-tooltip?example=5e81d946443f4900195606b7

import React, { Component } from 'react';
import { RefTooltip } from '@bit/bit.base.atoms.ref-tooltip'

export default class ExampleUsage extends Component {
    state = { ref: [] };

    handleRef = (elem) => {
        if (this.state.ref.some(x => x === elem)) return;

        this.setState({ ref: [elem] });
    }

    render() {
        return (
            <div>
                <span ref={this.handleRef}>target</span>

                { /*
                   * (!)
                   * This .map() breaks tooltip
                   *
                   */ }
                {this.state.ref.map((elem, idx) => (
                    <RefTooltip key={idx} targetElement={elem}>
                        "tooltip"
                    </RefTooltip>
                ))}
            </div>
        );
    }
}
//ref-tooltip.tsx
import React, { Component } from 'react';
import classNames from 'classnames';

//@ts-ignore
import createRef from 'react-create-ref';
import { createPopper, Instance, Options } from '@popperjs/core';

import styles from './ref-tooltip.module.scss';

export type RefTooltipProps = {
    targetElement?: HTMLElement;
    popperOptions?: Partial<Options>;
} & React.HTMLAttributes<HTMLDivElement>;

export class RefTooltip extends Component<RefTooltipProps> {
    private ref = createRef();
    private popperInstance?: Instance;

    componentWillUnmount() {
        this.destroy();
    }

    componentDidUpdate(prevProps: RefTooltipProps) {
        const nextProps = this.props;

        if (prevProps.targetElement !== nextProps.targetElement) {
            this.reposition(nextProps.targetElement);
        }
    }

    private reposition = (targetElement?: HTMLElement) => {
        const { popperOptions = popperDefaultOptions } = this.props;
        const popperElement = this.ref.current;

        if (!targetElement) {
            this.destroy();
        }

        if (!targetElement || !popperElement) return;

        this.popperInstance = createPopper(targetElement, popperElement, popperOptions);
    };

    private destroy() {
        if (!this.popperInstance) return;

        this.popperInstance.destroy();
        this.popperInstance = undefined;
    }

    render() {
        const { className, targetElement, ...rest } = this.props;
        return (
            <div
                {...rest}
                ref={this.ref}
                className={classNames(styles.tooltipWrapper, className)}
                data-ignore-component-highlight
            />
        );
    }
}

const popperDefaultOptions: Partial<Options> = {
    placement: 'top',
    modifiers: [
        {
            name: 'flip',
            enabled: false,
        },
    ],
};

Expected:
enter image description here

Actual:
enter image description here

I don't understand why the .map() breaks popper. At least for an array of 1, it should behave the same. Any ideas why this isn't working?

Uri Kutner
  • 476
  • 4
  • 13

0 Answers0