title: D3 + React hooks + useEffect() - prevent duplicate calls to d3.append() on mouseout
body: <p>Hi everyone here's a fun one with D3, topojson, and React hooks.</p>
  <p>I'm hung up on removing the instance of a tooltip that gets created on mouseover and persists on mouseout -- the &quot;React hooks&quot; way of working with D3 is to use <code>useEffect()</code>, the svg is rendered with <code>useRef()</code>, and that works fine, but each successive mouseover event triggers a new <code>.append</code> that adds another div so the tooltip content just keeps getting piled on.</p>
  <p>I thought that using D3's <code>selection.join()</code> might be the way to handle this, but not working yet. I've definitely considered implementing a completely separate tooltip component in React that shares context with the chart component but first I'm hoping someone might have an idea about how to do this with D3 since it's close.</p>
  <p>How can the &quot;extra&quot; <code>div</code> content getting added with <code>.append</code> be removed on mouseout with D3?</p>
  <p><a href="https://codesandbox.io/s/modest-dream-y0ui8?file=/src/MapChart.js" rel="nofollow noreferrer">Working example on Codesandbox here.</a></p>
  <p>Relevant code:</p>
  <pre><code>    svg
        .append(&quot;g&quot;)
        .selectAll(&quot;path&quot;)
        .data(features)
        .join(
          (enter) =&gt;
            enter
              .append(&quot;path&quot;)
              .attr(&quot;fill&quot;, (d) =&gt;
                colorScale(turnoutPerc.get(d.properties.GEOID))
              )
              .attr(&quot;d&quot;, path)
              .on(&quot;mouseover&quot;, (event, d) =&gt; {
                d3.select(event.currentTarget)
                  .raise()
                  .style(&quot;stroke&quot;, &quot;#000&quot;)
                  .style(&quot;stroke-width&quot;, 2)
                  .style(&quot;cursor&quot;, &quot;pointer&quot;);
                event.preventDefault();
                let [x, y] = d3.pointer(event);
                let ttPos = positionTooltip(x, y);
                let countyData = {
                  showTT: true,
                  FIPS: d.properties.GEOID,
                  mouse: { x: ttPos.x, y: ttPos.y }
                };
                updateCurrentCounty(countyData);
                let countyProps = getCountyProps(countyData, data);
                container
                  .style(&quot;visibility&quot;, &quot;visible&quot;)
                  .style(&quot;top&quot;, () =&gt; {
                    let ttHeight = 400;
                    if (y &lt; ttHeight / 2) {
                      return event.pageY - 50 + &quot;px&quot;;
                    } else if (y &gt;= ttHeight / 2 &amp;&amp; y &lt; height - ttHeight / 2) {
                      return event.pageY - ttHeight / 2 + &quot;px&quot;;
                    } else {
                      return event.pageY - ttHeight + &quot;px&quot;;
                    }
                  })
                  .style(&quot;left&quot;, event.pageX + 60 + &quot;px&quot;);
  
                  // Header
                  const header = container
                    .append(&quot;div&quot;)
                    .attr(&quot;class&quot;, &quot;tt-header&quot;);
                  const location = header
                    .append(&quot;div&quot;)
                    .attr(&quot;class&quot;, &quot;tt-location&quot;);
                  location
                    .append(&quot;h1&quot;)
                    .attr(&quot;class&quot;, &quot;tt-county&quot;)
                    .html(countyProps.countyName);
                  location
                    .append(&quot;h2&quot;)
                    .attr(&quot;class&quot;, &quot;tt-state&quot;)
                    .html(countyProps.state);
              })
              .on(&quot;mouseout&quot;, (event, d) =&gt; {
                updateCurrentCounty({
                  showTT: false,
                  FIPS: null
                });
                d3.select(event.currentTarget)
                  .style(&quot;stroke&quot;, &quot;none&quot;)
                  .style(&quot;cursor&quot;, &quot;default&quot;);
              }),
          (update) =&gt; update.call((update) =&gt; update.transition(t)),
          (exit) =&gt; exit.remove()
        );
  
  </code></pre>
  <p><a href="https://i.stack.imgur.com/VBw8Y.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/VBw8Y.gif" alt="enter image description here" /></a></p>