简体   繁体   中英

Append react component on click

I'm trying to write a Tooltip component that appears on click on element. I've seen implementation of this kind of component where tooltip is kind of wrapped around another element, but that is not suitable for me. I was thinking that maybe since I can access event.target I'll just select target's parent, then create new instance of a tooltip component with absolute positioning calculated from position of a target and append it to a parent, but I get an error that new instance of my Tooltip is not of node type and so it's not a valid child. TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.

Tooltip is a class component and it returns just a <div> with some text:

class Tooltip extends React.Component {
    constructor(props) {
        super(props);
    }

    basicStyle = {
        width: 80,
        height: 30,
        backgroundColor: '#333333',
        position: 'absolute',
        left: this.props.tooltipLeft,
        right: this.props.tooltipTop,
    };

    render() {
        return <div style={this.basicStyle}>tooltip</div>;
    }
}

And here's code for this demo app:

import React from 'react';
import Tooltip from './Tooltip';
import './App.css';

function App() {
    const clickHandler = (event) => {
        //get coordinates to position a Tooltip
        const pos = event.target.getBoundingClientRect();
        const tooltipLeft = pos.left + pos.width / 2;
        const tooltipTop = pos.top - 20;

        const tooltip = new Tooltip(tooltipLeft, tooltipTop);

        const parent = event.target.parentNode;
        parent.appendChild(tooltip);
    };

    return (
        <div className='wrapper'>
            <div className='box one' onClick={clickHandler}></div>
            <div className='box two' onClick={clickHandler}></div>
            <div className='box three' onClick={clickHandler}></div>
        </div>
    );
}

export default App;

I've also tried do the same thing with useRef and ReactDOM through use of portals (not sure if it's right way of using them), but it didn't help, even though error message no longer show up on click, just nothing happens. In this case the code I used is following:

import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import Tooltip from './Tooltip';
import './App.css';

function App() {
    const parentRef = useRef(null);

    const clickHandler = (event) => {
        //get coordinates to position a Tooltip
        const pos = event.target.getBoundingClientRect();
        const tooltipLeft = pos.left + pos.width / 2;
        const tooltipTop = pos.top - 20;

        const tooltip = new Tooltip(tooltipLeft, tooltipTop);
        ReactDOM.createPortal(tooltip, parentRef.current);
    };

    return (
        <div className='wrapper' ref={parentRef}>
            <div className='box one' onClick={clickHandler}></div>
            <div className='box two' onClick={clickHandler}></div>
            <div className='box three' onClick={clickHandler}></div>
        </div>
    );
}

export default App;

I'm feeling like I'm missing something obvious here but I can't get it. Can someone explain to me what I'm doing wrong in each of two attempts?

UPDATE: I realized I should have said a bit more: A little explanation of what I'm trying to achieve. Basicly I'm building a site with the ability to customize every element on it and that is the role of this tooltip component. I'm not using conditional rendering here because that would require me to manually add hooks/conditions to every possible place a Tooltip could be called or use just one instance of it with different possitions and content. I need to be able to show multiple Tooltips for a big number of elements and they should be able to be opened at the same time, this is why I went with class component and appendChild().

You should use the clickhandler to update your state, then conditionally draw the tooltip according to the state. I admit that I haven't used portals before so I'm not sure about that, but the "state" way is pretty straightforward.

Basically something like this:

const [pos,updatePos] = useState(null)

const clickHandler = (event) => {
    updatePos(event.target.getBoundingClientRect())
}

return (
        <div className='wrapper' ref={parentRef}>
            <div className='box one' onClick={clickHandler}></div>
            <div className='box two' onClick={clickHandler}></div>
            <div className='box three' onClick={clickHandler}></div>
            { pos && (<ToolTip position={pos}/>) }
        </div>
    );

So it looks like you can't just stright up append react component using appendChild() , since appendChild() is a native javascript function and return value of react component(or in case of classes - of its render() method) is just object and not a node in any way (since react manages DOM for you). But I can't figure why portals don't work since I don't really know much about them.

If anybody interested, I also managed to write a custom hook/component that controls tooltips. I ended up using useState hook to store array all of the active tooltips and array of elements on which those tooltips was called. It's just a raw prototype, but it's working. I'll add some functionality and polish it a bit later, but for now it's just a tooltip dummy.

https://codepen.io/ClydeTheCloud/pen/MWKJQza

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM