Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Popover does not show up in React

I am trying to make a webapplication with Treeviz dependency. The goal is to place a popover button to each node of the tree and if user clicks to the button he/she can see the description of the node,and after it should be editable. I tried in many ways but for me popover does not work in React.

There is an example for what I would like to do. You can see I have to insert React component to HTML therefor I am using renderToString. All you have to look is the renderNode property of the tree. I am referencing to React component in renderNode like: ${tooltip} ${popover}.

import React from "react";
import { TreevizReact } from "treeviz-react";
import { renderToString } from "react-dom/server";
const data_1 = [
  {
    id: 1,
    text_1: "Chaos",
    description: "Void",
    father: null,
    color: "#FF5722"
  },
  {
    id: 2,
    text_1: "Tartarus",
    description: "Abyss",
    father: 1,
    color: "#FFC107"
  },
  { id: 3, text_1: "Gaia", description: "Earth", father: 1, color: "#8BC34A" },
  { id: 4, text_1: "Eros", description: "Desire", father: 1, color: "#00BCD4" }
];

export const App = () => {
  return (
    <div style={{ marginLeft: 10 }}>
      <div style={{ display: "flex" }}>
        <TreevizReact
          data={data_1}
          relationnalField={"father"}
          nodeWidth={120}
          nodeHeight={80}
          areaHeight={500}
          areaWidth={1000}
          mainAxisNodeSpacing={2}
          secondaryAxisNodeSpacing={2}
          linkShape={"quadraticBeziers"}
          renderNode={(data) => {
            const nodeData = data.data;
            const settings = data.settings;
            let result = "";
            const tooltip = renderToString(
              <strong
                data-toggle="tooltip"
                data-placement="top"
                title={nodeData.description}
              >
                {nodeData.text_1}
              </strong>
            );
            const popover = renderToString(
              <button
                type="button"
                className="btn btn-secondary"
                data-container="body"
                data-toggle="popover"
                data-placement="top"
                data-content={nodeData.description}
              >
                Popover on top
              </button>
            );
            if (data.depth !== 2) {
              result = `<div className="box" 
              style='cursor:pointer;height:${settings.nodeHeight}px; width:${settings.nodeWidth}px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:${nodeData.color};border-radius:5px;'>
              <div>
          ${tooltip}
          ${popover}
          </div></div>`;
            } else {
              result = `<div className="box" style='cursor:pointer;height:${
                settings.nodeHeight
              }px; width:${
                settings.nodeHeight
              }px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:${
                nodeData.color
              };border-radius:${settings.nodeHeight / 2}px;'><div><strong>
          ${nodeData.text_1}
          </strong></div></div>`;
            }
            return result;
          }}
          duration={600}
          isHorizontal
          linkWidth={(node) => 10}
        />
      </div>
    </div>
  );
};
export default App;

Tooltip is working but popover does not show up. enter image description here

You can try it: https://codesandbox.io/s/zealous-orla-4bq5f?file=/src/App.js

Also tried

const popover = renderToString(
              <Popup
                trigger={<button> Trigger</button>}
                position="right center"
              >
                <form onSubmit={saveHandler}>
                  <ContentEditable
                    html={text.current}
                    onChange={handleChange}
                  />
                  <button type="submit">Save</button>
                  <button>Cancel</button>
                </form>
              </Popup>
const popoverContent = (
              <Popover id="popover-basic">
                <Popover.Header as="h3">Popover right</Popover.Header>
                <Popover.Body>
                  And here's some <strong>amazing</strong> content. It's very
                  engaging. right?
                </Popover.Body>
              </Popover>
            );

            const popover = renderToString(
              <OverlayTrigger
                trigger="click"
                placement="right"
                overlay={popoverContent}
              >
                <Button variant="success">Click me to see</Button>
              </OverlayTrigger>
            );

None of them worked for me.

like image 980
Phaki Avatar asked Sep 16 '21 10:09

Phaki


1 Answers

Probably your approach doesn't work because the dom elements in the tree are created dynamically, and bootstrap doesn't set them up.

A more react-ish way to do it would be using react-bootstrap lib and managing every UI aspect in states. To implement the tooltip, the Overlay component actually as a prop called target that allows you to change over what element the tooltip is shown.

    <Overlay target={tooltipTarget} show={showTooltip} placement="right">
      {(props) => (
        <Tooltip id="overlay-example" {...props}>
          {tooltipNode?.data.text_1}
        </Tooltip>
      )}
    </Overlay>

Then you only need to manage all these states in the onNodeMouseEnter and onNodeMouseLeave handlers in the TreevizReact component.

        onNodeMouseEnter={(_event, node) => {
          const t = document.querySelector(`#node-${node.id}`);
          setTooltipTarget(t);
          setShowTooltip(true);
          setTooltipNode(node);
        }}
        onNodeMouseLeave={(_event, node) => {
          setTooltipTarget(null);
          setShowTooltip(false);
          setTooltipNode(null);
        }}

The popup follows the same logic with another set of states.

  <div ref={ref}>
    <Overlay
      show={!!selectedNode.current}
      target={target}
      placement="bottom"
      container={ref.current}
      containerPadding={20}
    >
      <Popover id="popover-contained">
        {/* hack to avoid weird blinking (due mutable state) */}
        {!!selectedNode.current && (
          <>
            some form based on node data
            {JSON.stringify(selectedNode.current?.data)}
          </>
        )}
      </Popover>
    </Overlay>
  </div>

and in onNodeClick handler

        onNodeClick={(_event, node) => {
          const t = document.querySelector(`#node-${node.id}`);
          // unselect if already selected
          if (selectedNode.current?.id === node.id) {
            selectedNode.current = null;
            setTarget(null);
          } else {
            selectedNode.current = node;
            setTarget(t);
          }
        }}

You might notice this time I used a mutable variable via a ref (selectedNode.current), for some reason using a state caused some issues, maybe due to D3 and react having different rendering cycles, reason why you'll probably encounter some other glitches in this implementation.

https://codesandbox.io/s/quizzical-buck-slofh?file=/src/App.js

like image 198
diedu Avatar answered Oct 19 '22 06:10

diedu