最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - update dom ref using d3 and react - Stack Overflow

programmeradmin0浏览0评论

I have a functional ponent with a d3 function which appends a p to the ref on mount

Im trying to change the text content of the appended paragraph using the useState hook

clicking the button <button type="button" onClick={(e)=> setData("bar")}>change data</button> triggers a re-render, instead of changing the text of the p tag , a new version of the d3 function gets created --> appends a new p

reading How to rerender when refs change suggest to use the useCallback hook which im not quit sure how implement

how to achive a a re-render of the paragraph without creating a new one?

import {useEffect, useState, useRef} from 'react'

import * as d3 from 'd3'

function DomReference() {

  const domRef = useRef()

  const [data, setData] = useState("foo")

  useEffect(()=>{


    d3.select(domRef.current)
      .append('p')
      .text(data)
    
    // re-render --> creates a new function 
    
  }, [data])


  return (
    <div ref={domRef}>
      <button type="button" onClick={(e)=> setData("bar")}>change data</button>
      
    </div>
  )
}

export default DomReference

I have a functional ponent with a d3 function which appends a p to the ref on mount

Im trying to change the text content of the appended paragraph using the useState hook

clicking the button <button type="button" onClick={(e)=> setData("bar")}>change data</button> triggers a re-render, instead of changing the text of the p tag , a new version of the d3 function gets created --> appends a new p

reading How to rerender when refs change suggest to use the useCallback hook which im not quit sure how implement

how to achive a a re-render of the paragraph without creating a new one?

import {useEffect, useState, useRef} from 'react'

import * as d3 from 'd3'

function DomReference() {

  const domRef = useRef()

  const [data, setData] = useState("foo")

  useEffect(()=>{


    d3.select(domRef.current)
      .append('p')
      .text(data)
    
    // re-render --> creates a new function 
    
  }, [data])


  return (
    <div ref={domRef}>
      <button type="button" onClick={(e)=> setData("bar")}>change data</button>
      
    </div>
  )
}

export default DomReference

Share Improve this question asked Jun 23, 2021 at 12:26 SatorSator 7765 gold badges17 silver badges40 bronze badges 0
Add a ment  | 

2 Answers 2

Reset to default 3 +50

The d3 append function will always create a new element whenever it is executed.

To have always one paragraph and only update its value, you can use d3.join.

instead of:

  d3.select(domRef.current)
      .append('p')
      .text(data)

You could use:

  d3.select(domRef.current).selectAll('p')
    .data([data])
    .join('p')
    .text(d => d)

Explaining line by line:

  • d3.select(domRef.current).selectAll('p'): Selects all the <p> elements in domRef.current
  • .data([data]): binds the <p> elements to an array of data. When you bind, each item in the array will have its own <p>.
  • .join('p'): The join will create a selection that removes all the <p> that do not have data associated with, and will update the ones that have data. This will leave you with one <p>, since the .data() call before only had one element in the array
  • .text(d => d): This callback is how you access the data that is associated with the <p>.

As a plement:

if you don't want to remove all the other <p>, you can use a class to narrow your selection:

  d3.select(domRef.current).selectAll('p.myOddParagraphs')
    .data([1, 3, 5])
    .join('p')
    .classed('myOddParagraphs', true)
    .text(d => d)

   d3.select(domRef.current).selectAll('p.myEvenParagraphs')
    .data([2, 4, 6])
    .join('p')
    .classed('myEvenParagraphs', true)
    .text(d => d)

The code above would result in 6 paragraphs: three are bound to the [1, 3, 5] array, and the other three are bound to [2, 4, 6]

In principle, you shouldn't attempt to mutate DOM using D3 while using React, because React would be unaware of such changes. This usually leads to very nasty bugs that are difficult to spot and debug. If you find yourself using attr or append in React, you're using it wrong ;)

This is not to say you can't use D3 with React though. The correct way is to always leave DOM manipulations to React, and use D3 as engine that putes your properties. E.g. when using d3 force layout, you can pute (x,y) coordinates using D3, and pass them as props, so that React handles reconciliation etc.

In the above example, I'd suggest leaving the rendering of <p .../> to React. I don't know however what is your end goal so can't really tell what's the correct way, apart from converting this example trivially to React

发布评论

评论列表(0)

  1. 暂无评论