d3 version is clear, flexible and composable but react version seems hacky and limited.
I think putting d3 in useEffect is pretty perfect way of merging d3 and React DOM operations.
What are the drawbacks?
I agree with you here but
> there is also huge performance benefits in letting only React control the DOM.
Compared to using d3 to operate on the DOM directly?[0] Do you have any benchmarks to back that up? Because in my experience, d3 is incredibly fast. Then again, maybe we're thinking about different applications? (Most of my experience is with animating stuff with d3.)
[0]: Whether or not React is used alongside with it should not matter much, I think, as long as one doesn't trigger re-rendering of the React DOM.
It also isn't re-inventing the wheel. d3 and React are based on fundamentally different models of programming (d3 is very imperative). I have done two or three pretty complex charting apps, and the only way to really break that cycle is to use the dispatcher which allows you to break up the flow of d3...you still end up with a ton of cruft, and lots of confusing constructs (you have to be very specific about what state is needed where and when...for example, some constructs in d3 require data immediately i.e. an axis, and others do not i.e. line, and some constructs require other constructs i.e. the dependency between lines and axis...clearly this conflicts with the data flow of your typical React app quite heavily).
There's a point in this article where they've replaced D3's axes code with a new React implementation. "Okay! So this is definitely more code. But that makes sense, since we're basically duplicating some of the d3 library code, in our own code base." I mean, yes. It makes sense. But it is, ceteris paribus, way worse than D3, where axes generation is kind of a fundamental basic idiom of the library, which we're here replacing with idiosyncratic new code.
I get that the code might be better in some other direction, but just as code, it's worse. The claim is that it's more readable, but: it's only more readable if you don't do a lot of D3. To me this is shades of people trying to talk about Lisp function names or APL or whatever.
I 100% agree though, it is very janky. The issues aren't limited to the points you mention: state everywhere, errors getting swallowed, problems when you update (for example, the Axis component has a useMemo that renders once on load and doesn't change...this isn't a good idea because, presumably, you have to re-render on every update), on and on. The issue isn't d3 but making components reusable within React.
d3's dispatch model fits well with React. But the data flow/dependencies just don't work with React at all (i.e. having to declare operations in a certain order). I am not 100% sure that the component library approach works. I get the idea but you need to actually tackle the problem of state and updating, which means working with the imperative model (i.e. not declaring your chart elements in React but using d3's imperative code, and hooking that into React's update cycle...for example, how would you use d3-brush with declarative code? Maybe I am missing something but it doesn't seem possible). I have no idea whether it is possible to integrate these two approaches fully...probably is but I surely don't know how (my approach so far is largely imperative, relies heavily on dispatching events, and splits state between data/chart constants/individual charts...I think it is possible to go further with hooks but I haven't had time to explore this fully...the declarative approach of actually writing svg elements and g elements seems too brittle).
That cannot be true. D3 is directly optimized for efficient updates, vs the comparatively inefficient virtual dom + diffing. Every performance oriented tool actually skips the react rendering lifecycle (even redux does that).
Unless, of course, you’re triggering re-renders of the element D3 is controlling, which should not be the case.
How do you prove that with numbers?
Regarding how I should confirm this is the case, I'd just run a controlled experiment and compare the frame rate produced by the two approaches.
1. People make good faith assumptions on performance all the time and those assumptions tend to be either wrong or wildly inaccurately. The most important part of a performance difference is not that something is faster, but by how much faster it is. Without numbers to know the size of difference the conversation becomes meaningless.
2. People, particularly in the context of favorite frameworks and the DOM, regularly speak to performance complete out of context. They believe they are comparing like items when they aren't even remotely related. When it comes to performance there are typically only two things that are measured: execution time on the CPU or human writing time. When it comes to CPU execution time you need to start from the same state and end with an identical product for the comparison to be valid. A comparison between a React component and DOM manipulation aren't comparable enough to buy you that comparison. Its like comparing the production of a hammer factory to the speed of a nail gun.