Home / Articles

D3.js Inside React: Data Binding, SVG and the useEffect Ownership Rule

Learn what D3 really is, how enter/update/exit binding works, why SVG suits it, and how to split DOM control so D3 and React cooperate in a bar chart.

Publisher: REACTAPP.TOP7 min read1530 words

This article is published in English.

Sources: Harnessing the Power of Data Visualization: Integrating D3.js with React

Three things to know first

  1. D3 is a toolkit, not a chart library. People who arrive expecting something like Chart.js get tripped up. D3 is low-level machinery for tying data to DOM elements, which explains both its power and its steeper learning curve.
  2. React and D3 compete for the same DOM nodes. The cure is straightforward: React renders the SVG container, and D3 manages everything inside it from a useEffect. Skip that rule and you'll spend hours chasing odd conflicts.
  3. The enter, update and exit pattern feels strange at first. After a handful of encounters it clicks, and then other approaches to data-driven graphics seem clumsy.

What D3 actually is

The name stands for Data-Driven Documents. The word to notice is documents: DOM elements whose look is determined by data. D3 binds arrays to elements and lets you define appearance as a function of the values in those arrays. A chart is only one possible result, and the library itself has no notion of chart types.

That's where the strength and the difficulty both come from. D3 makes no decisions on your behalf. Every visual property, every axis tick, every colour and every interaction is something you specify. You get total creative freedom and a lot of typing.

Data binding and the DOM

This is where most newcomers give up, so it's worth walking through slowly.

In ordinary JavaScript, you create an element, fill it with content and insert it into the page. With D3, you select a group of elements, including ones that don't exist yet, bind an array to that selection, and then receive three groups back:

  • elements that already exist and just need fresh data (the update selection),
  • data points that have no element yet (the enter selection),
  • elements whose data has disappeared (the exit selection).

The odd part is calling methods on a selection that is empty. You select all rect elements inside an SVG that contains none, call .data(myArray), then .enter(), then .append('rect'), and rectangles appear. What's happening is that .enter() yields a placeholder for every datum lacking an element, and .append() turns each placeholder into a real node. Read it as "for every data point without an element, make one."

Before this clicks, D3 code looks like sorcery. Afterwards it reads naturally.

Setting visual properties from data

Once data is bound, the next step is easier to grasp. Any attribute or style can be computed from the datum:

selection.attr('height', d => yScale(d.value))

Here d is the datum attached to that particular element, and yScale is a function converting your value range into pixels. The bar's height is a direct function of its data. Alter the data, run the line again, and the height follows. Nearly everything in a D3 visualization boils down to variations of this idea: set a visual property to a function of the bound value.

Why SVG

D3 draws into SVG, and the reasons go beyond history.

SVG is vector-based, so shapes are defined mathematically instead of as pixel grids. A bar chart that's crisp on a laptop is equally crisp on a 4K display with no extra effort, and exports don't turn blurry at awkward resolutions.

More important for interactivity, SVG shapes are ordinary DOM elements and take part in the browser's event system just as a button does. Hover, click and touch all work with no custom hit-testing, because you simply attach listeners.

The cost shows up at scale. Thousands of SVG nodes slow the page down, so plotting 50,000 separate points as individual elements will hurt. Canvas rendering fits that situation better. For dashboards, business charts and most analytical tools, SVG is fine and the event integration is worth it.

Interactivity and flexibility

Data-exploration interfaces, where users slice, filter and inspect data from various angles, are where D3 outshines static charting tools. You control every event and transition.

Most chart libraries hand you chart types to configure: bar, line, pie, with options for colours, labels and legends. D3 supplies building blocks instead: scales, axes, path generators and layout algorithms. A chart type is just a combination of them, so there's no list of what is supported. If a visualization doesn't exist anywhere, you can build it, limited only by SVG and mathematics.

The price is extra work. Whether it's worth it depends on the project. For standard business charts, Recharts or Chart.js will do, and you can move on. For custom, interactive graphics where presentation matters exactly, D3 is the better route.

The functions you'll use constantly

This isn't a full reference, only the calls that appear in everyday work:

  • d3.select() grabs the first element matching a selector. In React, d3.select(svgRef.current) is the usual way to get hold of the container.
  • d3.selectAll() returns every match. Data binding generally starts here because several elements are involved.
  • selection.data() joins each array item to an element, so d is available in later calls. Everything else depends on this step.
  • selection.enter() gives the placeholder selection for data that has no node, and .append() after it creates the nodes.
  • selection.attr() sets attributes, for instance the height line shown above, which you'll write hundreds of times.
  • selection.style() does the same for CSS, such as a fill colour or an opacity that depends on whether an item is active.
  • selection.transition() animates changes. Put it before .attr() or .style() and values interpolate smoothly, turning a jump into an animation.
  • d3.scaleLinear() converts values from one numeric interval into another. With a domain of 0 to 100 and a range of 0 to 400, a value of 50 lands at pixel 200. Scales appear in virtually every visualization.
  • d3.axisBottom() and d3.axisLeft() produce axes with ticks and labels from a scale, saving you from drawing marks by hand.
  • d3.max() returns the largest value, often used to set scale domains, as in d3.max(data, d => d.value).
  • d3.transition() coordinates animations across multiple selections at once.

Using D3 inside React

The trouble nobody mentions until you hit it: React keeps a virtual DOM and applies its own changes to the real one, while D3 edits the real DOM directly. If both operate inside one element, React's re-render overwrites D3's work, or D3 inserts nodes that React doesn't know about and the next render gets confused.

The approach that works is dividing responsibility. React renders the SVG container and touches nothing inside it. D3 reaches that container through a ref and does all its work in a useEffect. When data changes, a state update triggers the effect, and D3 clears and redraws. Neither library interferes with the other.

1. Install the packages

Add D3 alongside React and ReactDOM with your package manager.

npm install d3 react react-dom

Yarn users do the same with its own add command.

yarn add d3 react react-dom

2. Write the component

The React side has one job: render an SVG element and hand D3 a ref to it. Nothing else inside the visualization belongs to React.

3. Draw in useEffect

Inside the effect, write D3 just as you would outside React, using the ref to find the container. Two scales do the coordinate math. One maps category labels to horizontal positions, and the other maps data values to vertical ones. Each bar's height is the chart's bottom edge minus the scaled value. This part trips people up because the SVG y-axis points downward, so taller bars correspond to smaller y values.

4. Render the component

Place the component in your app and pass it a data array. Change the array and the chart redraws: React notices the changed prop, the effect fires, and D3 clears and redraws. The link between application state and visualization is automatic.

5. Start the app

Run the dev server with npm.

 npm start

Or with Yarn.

 yarn start

You'll see a bar chart in the browser. From there, everything builds on the same pattern: tooltips on hover, colour scales, animated updates, zoom and further chart types. All of it lives in that effect and uses the functions listed above.

Wrapping the idea up

D3 and React cooperate once the structure is right. React owns the container and the component lifecycle, and D3 owns the drawing inside it. Keep that boundary clear and each library does what it's built for.

The reward is the ability to build visualizations no chart package ships, because you're working with primitives and not configuration options. When the graphic itself is part of the product's value, that matters. When a dashboard just needs a line chart, take Recharts and skip the ramp-up.