Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing findDOMNode #3979

Closed
mormahr opened this issue Feb 26, 2020 · 12 comments · Fixed by #6656
Closed

Removing findDOMNode #3979

mormahr opened this issue Feb 26, 2020 · 12 comments · Fixed by #6656

Comments

@mormahr
Copy link

mormahr commented Feb 26, 2020

Update:
I think my analysis about how this can be solved is wrong. See my comment.


As part of the move to strict and concurrent mode support, I'd like to tackle removing usages of findDOMNode. I analyzed the scope of changes needed in the first-party components and possible implications on third-party consumers.

Alternatives to findDOMNode

Refs (preferred)

Refs are probably the standard way to get access to an HTML node. Instead of explicitly wiring refs through the consuming component, they can often be set through cloneElement.
Downsides:

  • Can reference JSX elements instead of DOM nodes

Workarounds:

  • Add a warning in the documentation and print a runtime warning
  • Some components already only allow a single DOM node as a child (e.g. ResizeSensor)
    • Can clone the element and attach a ref that way

Wrapper element

Downsides:

  • Can cause problems with DOM nesting and layout
  • Possible performance overhead

Workarounds:

  • display: contents; (doesn't work all most usages)
  • Provide tagName, etc. properties

Usages

ResizeSensor

ResizeSensor uses findDOMNode to attach a ResizeObserver to the child node. It requires a single DOM node child and can use cloneElement to attach a ref. I don't know about the performance implications of cloneElement (resize sensor explicitly mentions this in the source comments), but ContextMenuTarget also uses cloneElement. My understanding is that cloneElement is pretty cheap. I don't even think it's slower than findDOMNode, so I'd say cloneElement and attaching a ref is the way to go here.

ContextMenuTarget

The element returned by findDOMNode is used to check if dark mode is enabled or not, by calling a helper method. ContextMenuTarget already requires a DOM node and uses cloneElement to attach the event-listener so that we can attach a ref in the same call.

Overlay

If I understand this correctly TransitionGroup creates a container by instantiating the component passed to it. As Overlay requests a div, this div is the element we want a reference to. I currently don't know how to get a ref to this div. Afaik TransitionGroup doesn't expose a reference. This needs some further investigation.

Conclusion

Without considering Overlay, I'd say this is doable without breaking changes. I would split this into three separate tasks.

  1. Using cloneElement in ContextMenuTarget to attach a ref is a non-breaking change.
  2. Using cloneElement in ResizeSensor to attach a ref is a non-breaking change.
  3. Overlay needs some further investigation.

There have been previous issues that mentioned findDOMNode. For reference:

@LoiKos
Copy link

LoiKos commented Sep 22, 2020

Hi there,

I comment here in this issue because i get a lot of struggle with refs due to the fact their aren't forwarded (e.g: Card). It get better since you add some elementRef to some components. It's just for "Basic" component like Card, Button,... e.g the way Suggest is build is totally fine.

From react docs :

In most cases, you can attach a ref to the DOM node and avoid using findDOMNode at all.

I don't know if it's in your planning right now but i think i will be a great improvement.

@adidahiya adidahiya self-assigned this Sep 22, 2020
@mormahr
Copy link
Author

mormahr commented Oct 8, 2020

Oh well, I wanted to comment on this for a long long time. I tried to get this to work without breaking the API (by explicitly passing the ref), but I'm pretty sure it's impossible. I learned a lot about cloneElement / createElement along the way. Unfortunately what I learned was, that it doesn't work how I thought it would work. I think the only way to do this (and the "right" way) is to indeed manually forward the refs. So the whole conclusion, that it can be done without breaking changes, seems highly unlikely. Except if someone smarter than me figured out how to do it.

I'm looking forward to the day blueprint will be strict mode ready!

@adidahiya
Copy link
Contributor

Thanks for investigating @mormahr. I took a look at this last week as well and attempted to do it without breaking API; I was unable to do so. Once React 17 is released and all our internal applications are migrated to React 16 (likely by the end of this year), I will make it a priority to work on this issue and other 4.x breaking changes.

@adidahiya
Copy link
Contributor

fixed by #4623, released in 4.0.0-alpha.0

@adidahiya
Copy link
Contributor

Re-opening since this is now part of v5.0

@switz
Copy link
Contributor

switz commented Jun 7, 2022

I'm still seeing this when using OverflowList in "@blueprintjs/core": "^4.3.2",

Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Blueprint4.ResizeSensor which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node

@adidahiya
Copy link
Contributor

Yep, I'm aware that Blueprint v4 still has incompatibilities with React strict mode. I'm hoping to get this resolved in the next 6-8 weeks, once I've made more progress on the Popover2 migration work.

@malechite
Copy link

@adidahiya How is the Popover2 migration going? I'd like to use Tooltip2 and Popover2 in React strict mode as soon as it's ready :)

@adidahiya adidahiya modified the milestones: 5.0.0, 6.0.0 Aug 22, 2022
@adidahiya
Copy link
Contributor

Unfortunately I've had to reduce the scope of Blueprint v5.0 in order to ship it faster. React strict mode compatibility is now scheduled for Blueprint v6.0.

@adidahiya
Copy link
Contributor

Most usage of findDOMNode has been removed at this point in v5.0.0-alpha.1, there is just one instance of it in the <Draggable> component. I might be able to fix that for the stable v5.0 release.

@adidahiya
Copy link
Contributor

I removed the last instance of findDOMNode in Blueprint code via #6137.

Last remaining item is to use the nodeRef feature on <CSSTransition> so that our dependencies don't use findDOMNode either. See https://github.com/reactjs/react-transition-group/blob/2989b5b87b4b4d1001f21c8efa503049ffb4fe8d/CHANGELOG.md?plain=1#L41-L56

@adidahiya adidahiya modified the milestones: 6.0.0, 5.0.0 May 9, 2023
@adidahiya
Copy link
Contributor

adidahiya commented May 15, 2023

Some decomp on the problem of migrating Overlay to use react-transition-group's nodeRef feature:

  • This will require Overlay's children to support the ref property so that we can inject a ref, similar to our requirements for ResizeSensor2
  • We can't add this requirement without a breaking change, so it will have to come through a "V2" component named Overlay2
    • We can migrate Blueprint's internal overlay components (Dialog, Drawer, Popover, OverlayToaster, OverlayExample, Omnibar) to use Overlay2 in v5.0 v5.x (this is straightforward since they all use a <div> container element which supports ref)
  • Consumers of <Overlay> will have to migrate to <Overlay2> to stop using findDOMNode
  • Overlay2 will replace Overlay in Blueprint v6.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

5 participants