-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Deprecate findDOMNode in StrictMode #13841
Deprecate findDOMNode in StrictMode #13841
Conversation
Does it mean all possible usages of |
Details of bundled changes.Comparing: 0af8199...665596c react-dom
react-reconciler
react-native-renderer
scheduler
Generated by 🚫 dangerJS |
Only in strict mode — yes, all usage. Forwarding refs should be sufficient. |
26d9a58
to
2fe2ac5
Compare
There are two scenarios. One is that we pass a component instance that is already in strict mode or the node that we find is in strict mode if an outer component renders into strict mode. I use a separate method findHostInstanceWithWarning for this so that a) I can pass the method name (findDOMNode/findNodeHandle). b) Can ignore this warning in React Native mixins/NativeComponent that use this helper. I don't want to expose the fiber to the renderers themselves.
2fe2ac5
to
8ba0db9
Compare
false, | ||
'%s is deprecated in StrictMode. ' + | ||
'%s was passed an instance of %s which is in a StrictMode subtree. ' + | ||
'Use an explicit ref directly on the element you want to get a handle on.' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about this:
%s was passed an instance of %s which is inside StrictMode. Instead, add a ref directly to the element you want to reference.
Also, do we even need "which is inside StrictMode"? It or ConcurrentMode should always appear in the stack unless they're using createRoot.
false, | ||
'%s is deprecated in StrictMode. ' + | ||
'%s was passed an instance of %s which renders a StrictMode subtree. ' + | ||
'The nearest child is in StrictMode. ' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"The nearest child is in StrictMode." feels redundant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, it is but I'm not confident people understand what that means so clarifying the mechanism might help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know that this is any clearer than the previous line though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed to "StrictMode children". I think that's clearer and fits in one line.
methodName, | ||
methodName, | ||
componentName, | ||
getStackByFiberInDevAndProd(fiber), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we pass hostFiber here too? Since you'll need to find it in order to change your code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should always be one the same stack since fiber is a child of hostFiber.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other way around, right? They have the same ancestors, but the hostFiber stack is longer/deeper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. You mean for the instance case. I figured that you'd want to look at refactoring the top component and then work downwards. But I guess we could use the same stack for both.
const fiber = ReactInstanceMap.get(component); | ||
if (fiber === undefined) { | ||
if (typeof component.render === 'function') { | ||
invariant(false, 'Unable to find node on an unmounted component.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know these are the same, but dev-only invariants feel risky.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea see what you're saying but refactoring this function is a pita and makes it worse.
Once we fix RN to not use the special NativeMixin/NativeComponent, we can remove this special case and always warn with the same function.
Notably this doesn't warn if no node is found. E.g. called on a strict mode component that renders null. I think this is fine because I think the next path would be that we make this always return null for strict mode. |
warningWithoutStack( | ||
false, | ||
'%s is deprecated in StrictMode. ' + | ||
'%s was passed an instance of %s which renders a StrictMode children. ' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"a StrictMode children" sounds odd, maybe drop "a"?
Hi, Is there any discussion or RFC about deprecating |
@sag1v I event wrote about it: It'll just make things even more complicated |
I might open an RFC for
What we do know is that the current API is broken and needs to be deprecated but we could potentially add a different API to support the one remaining use case. |
I haven't thought much about the Fragment-ref proposal, but initial reaction is that I like it, and can see myself using it. That being said– it doesn't help libraries, since they'll be stuck between the old way of doing things (e.g. This is like the gDSFP problem again. :) |
@sebmarkbage This looks solid and totally address the use case i mentioned here. |
@sebmarkbage, |
* Deprecate findDOMNode in StrictMode There are two scenarios. One is that we pass a component instance that is already in strict mode or the node that we find is in strict mode if an outer component renders into strict mode. I use a separate method findHostInstanceWithWarning for this so that a) I can pass the method name (findDOMNode/findNodeHandle). b) Can ignore this warning in React Native mixins/NativeComponent that use this helper. I don't want to expose the fiber to the renderers themselves.
@oliviertassinari I have the exact same use case and now the API is forced to expose a Edit Sometimes we want to get the DOM node without caring what type of children we get (function, text, class or regular DOM element).
Given this component:
We could use it with a simple API: But now with out
The consumer will use it this way:
|
@sag1v It's the best solution I can think of too. We will have to release a major on Material-UI side if it's the go-to approach and have people change their implication. But I think that it's laborious and error-prone (hard to debug when dealing with multiple level of components), they might be a better way 🤔. <TrapWithRef event="click">
{(trapped, ref) => <Box innerRef={ref} isFocused={trapped} />}
</TrapWithRef> |
I'm still confused why you have |
@gaearon
The only advantage for this approach is that the consumer can decide where to attach the |
I mean that you can pass
is necessary, and how that's relevant to the example above. |
I recently updated ReactFloatAnchor and ReactMenuList to no longer use findDOMNode, and I had to make changes similarly to @sag1v. ReactFloatAnchor takes @gaearon In my case, I couldn't use ref forwarding because MenuItem has many imperative methods and users need to be able to get a ref to the MenuItem itself too. |
@gaearon The fact that we don't understand each other is a good sign, something is off. Here is my understanding of the situation. As a library author, we don't control what the user's @jquense If I remember correctly, you were commenting about this problem 1-2 years ago. Maybe you could provide some light on the issue 💡, thanks! |
@gaearon I made a small and concise codesandbox example (a very naive implementation ) with just the parts that are relevant for this issue (from my angle at least, maybe @oliviertassinari has some more use-cases ). As you can see, both As for It may seem like a small change to the API but this is quite significant IMHO. it feels awkward and confusing (for the consumers). I'll be honest, there is an upside for this pattern though, The consumer gets to decide where or what exactly to "Trap". but this is a very small upside, IMO. |
Another concern i have for exposing an explicit Given this example, how can we use both
|
@sag1v We have an helper for that on Material-UI. But yes, it's a bit verbose otherwise. |
I would add we've tried generally to move to a findDOMNode-less, Honestly tho, the most frustrating problem I have is that we can't always tell if a user swallowed or dropped the ref we passed their component. Take the case of a dropdown/tooltip. We need two nodes, the toggle node, and the menu node. Once both both nodes exist we can pass them to Popper for positioning and calculation. Consider if they conditionally render the menu or toggle. We can't always warning that a ref wasn't used because they haven't used it yet, there are of course some workarounds and it's not intractable, but it has definitely increased bugs. Its ultimately the same sort of problem as props passing and middle components not passing through some prop. However the problem is worse because:
|
@oliviertassinari do you mean using both |
@sag1v With hooks ref composition may not be required const Component = () => {
const ref = React.useRef()
const rect = useElementResize(ref)
const trapped = useTrap(ref)
return (
<div ref={ref}>
<div> {trapped ? 'focused' : 'blur'}</div>
<div>{rect.width}</div>
</div>
)
} |
@TrySound Yeah i know, i already played with it 😄 |
@sebmarkbage Would the Fragment ref feature give refs to the direct child DOM nodes specifically, or just refs to whatever dom nodes or component instances are directly below? Example: class Foo extends React.Component {
render() {
return <div>aaaa <span>b</span></div>;
}
}
class Bar extends React.Component {
render() {
return <Fragment ref={arrayOfChildNodes => ...}> <Foo /> </Fragment>
}
} Would arrayOfChildNodes contain a Foo instance, or an HTMLDivElement instance? The DOM node behavior would be a useful findDOMNode replacement (styled-components/styled-components#2154, allowing react-float-anchor to not need the user to pass in a ref), though the component behavior would massively simplify the case where you want refs on a dynamic list of elements (See this SO thread about the situation. The accepted answer has a few minor issues. The other answer doesn't directly answer the question. I previously created an npm module specifically to solve this scenario.) and also the ref-sharing case (#13029). (Maybe fragment could support both as different props like |
For future reference — if you have specific problems that you feel you need |
Wrote an RFC for it. reactjs/rfcs#97 |
* Deprecate findDOMNode in StrictMode There are two scenarios. One is that we pass a component instance that is already in strict mode or the node that we find is in strict mode if an outer component renders into strict mode. I use a separate method findHostInstanceWithWarning for this so that a) I can pass the method name (findDOMNode/findNodeHandle). b) Can ignore this warning in React Native mixins/NativeComponent that use this helper. I don't want to expose the fiber to the renderers themselves.
Motivation
The main motivation is that findDOMNode is breaking abstraction levels but allowing a parent to reason about what kind of children a component might render. It creates a refactoring hazard where you can't change the implementation details of a component because a parent might be reaching into its DOM node.
Attaching explicit refs is the alternative. The main thing that used to be difficult is creating a higher order component that seamlessly work with its children. This can now be achieved using forwardRefs.
Up until this point it was a loose recommendation that you don't use this functions but there are certain technical details that are twisting our hands to want to deprecate it.
We'll only deprecate this in StrictMode for the foreseeable future. It won't get deleted for a long time because lots of existing code depends on it. This is more about preventing new code being written using this outdated technique.
PR
There are two scenarios. One is that we pass a component instance that is
already in strict mode or the node that we find is in strict mode if
an outer component renders into strict mode.
I use a separate method findHostInstanceWithWarning for this so that
a) I can pass the method name (findDOMNode/findNodeHandle).
b) Can ignore this warning in React Native mixins/NativeComponent that use this helper.
I don't want to expose the fiber to the renderers themselves.