-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Rule proposal: warn against using findDOMNode() #678
Comments
Some ideas to ease the transition: Automatically reference the top node rendered on the component class MyComponent extends Component {
componentDidMount() {
this._topDOMNode.scrollIntoView();
}
render() {
return <div />
}
} Somehow do shorthand aliasing for the ref prop: class MyComponent extends Component {
componentDidMount() {
this.node.scrollIntoView();
}
render() {
return <div ref={this.node} />
}
} Provide a reference to the DOM node separately from the element reference: class MyComponent extends Component {
componentDidMount() {
this.nodeRefs.myNode.scrollIntoView();
}
render() {
return <div ref="myNode" />
}
} |
In my opinion it seems like one of those cases where the only reason people want shortcuts is because they’ve been exposed to shorter magic syntax. Shorthands might seem “nice” but they actually make less sense coming from a beginner’s perspective. It’s easier to learn how the system works once than remember that |
@gaearon hey, can you leave a short note or is there a link to read why ref callbacks are preferred to ref strings? Thanks |
@gaearon Great idea! However, is this in addition to a warning within React itself? If the React team definitely wants to deprecate this, seems like it should definitely warn there, as well. Not everyone may use ESLint, and IMO, it's not ESLint's responsibility to notify users about feature deprecation. |
@notaurologist There isn't a feature deprecation, just a potentially bad pattern, which is definitely ESLint's domain. |
Suppose that I want to create a behavior component that acts on the DOM of its provided child (like a fake mutation observer, for instance): class Measure extends Component {
componentDidMount() {
const childNode = findDOMNode(this).children[0];
// Here I call whatever I want when the child is loaded
// @NOTE: There's no refs implied.
}
render() {
const { children } = this.props;
// Here, I'm agnostic to whatever the child might be, a pure function or a class
return children;
}
} Now, as I'm agnostic to the type children passed like this: <Measure>
<AnyChildWithoutRefsLikePureComponent/>
</Measure> I could clone the |
@gaearon small side note, in the after block - I believe the |
There's an example here of what @PEM-- described. Basically, it's an element that pins its children scroll position to bottom as it grows. const { render, findDOMNode } = ReactDOM
class PinToBottom extends React.Component {
/// ...
componentWillUpdate() {
const node = findDOMNode(this)
// ...
}
componentDidUpdate() {
if (this.pinToBottom) {
const node = findDOMNode(this)
node.scrollTop = node.scrollHeight
}
}
render() {
return React.Children.only(this.props.children)
}
} And then it can be used by any content container, like that: <PinToBottom>
<ul>
{lines.map((line, index) => (
<li key={index}>{line}</li>
))}
</ul>
</PinToBottom> I don't know how it could be made simpler by using callback refs or something else. |
How do you suggest one deals with higher order functions over non-dom components, such as var Wrapper = ComposedElement => class extends React.Component {
componentDidMount() {
// if ComposedElement is not a DOM component
// this.domNode <> ReactDOM.findDOMNode(this)
}
render() {
return <ComposedElement ref={r=>{this.domNode = r}}/>
}
}; |
@alaindresse why would that work any differently using |
I'm also wondering how is HOCs like this should be re-written then? https://github.com/okonet/react-container-dimensions/blob/master/src/index.js |
Your third example's "After" doesn't match with "Before". this.inputNode.scrollIntoView(); Should be this.inputNode.focus(); |
Actually, it seems to me that all examples drill down to the same behavior. If we could setup a callback ref on a children passed as prop, the A function like Sounds legitimate. A parent can call its children 😉 @gaearon, what do you think of this? |
Deprecating is adding a warning. So we plan to add a warning but in the future. I thought that maybe ESLint rule might be a better first idea before we deprecate it. |
@ljharb in the HOC, you don't know if ComposedElement is a DOM or a react class. The reference is then either a DOM node, or an instance of a react class. In the latter case, you need findDOMNode to get the actual dom node... One idea would be to have two arguments in the ref callback
if the component is a dom node, r===n. If the component is a react class, n is the topDOMNode @timdorr referred to earlier. |
Yes, HOCs like this would have to wrap their content in an extra People often request that React adds support for returning multiple nodes from render (“fragments”). Imagine we implement this. Now any component can either return zero, one, or many nodes. Somebody changes one of the “measured” components to return two nodes in some state. What should Whether a component returns many nodes is its implementation detail. It should be possible to change it without breaking any code in other components, but this would not work with our HOC. So the pattern itself is problematic. There are two solutions:
Reading nodes of child components is like wanting to access their state. This is not a pattern we should support or allow (even if it is technically possible). If it was unsupported from day one, I don’t think it would be much of a controversy. However it is less obvious that the pattern is problematic because it’s been possible for a while. |
You can as long as children are DOM elements. You can check for that with For reasons above you cannot do this on custom components. |
Indeed, I agree. It's like having a form that parses its input fields instead of giving the fields the capabilities to inform the form itself. It's parsing against eventing. And that's against the purpose of React 👍 DOM based operation should ensure that a parsable DOM is present. |
its possible to clone children with ref callback added to it, so it can be exposed to a wrapper component |
@Andarist: Thanks but it only works if your children are immediate DOM elements 😉 |
@Andarist @PEM-- Yes! The nice (or bad 😄) thing about <PinToBottom>
<List>
{lines.map((line, index) => (
<ListItem key={index}>{line}</ListItem>
))}
</List>
</PinToBottom> And it'll find its DOM node anyway.
One can argue that by using |
Just read @gaearon comments, and I agree 1000% with:
Now that I understand the problem better and how this make returning multiple nodes from render very difficult, I rewrote the example to wrap every children of It's much cleaner and doesn't break encapsulation! |
To come back to the ESLint rule
I'm agree, also adding a rule like this is pretty easy (we've already done something similar for |
As an additional example for those who need access to a component DOM node from its parent but also from the component itself, here is the modified code from @gaearon example: class Field extends Component {
render() {
return (
<input type='text' ref={node => this.props.inputRef(node) && (this.node = node)} />
)
}
} It seems somewhat trivial but it took me a bit of time to figure it out so it may help anybody with the same use case. |
It's ok now, got it to work. Instead of using |
Hey @sandy0201 I suspect that you're using it on a connected component. Anyway, the ref should give you access to dom elements that are created inside the class you're using the ref. You can't find a dom element on a component that part of I can't give you a precise solution without seeing the whole source, but it seems that this is not a problem related to React or the linter but how you're building your application. As a rule think of it this way: If that reference is applied to a component then the If this component is also connected using If you just console.log(this.input) you will get a javascript Object not a dom element because you're pointing it to a component which is connected to the redux store via |
Hi @andrevenancio , thanks so much for your detailed explanation, will have a look at my code again and try it out. :) |
@gaearon -- mind giving an update on this? Are you all still planning on removing |
For what it's worth, I subscribed to this thread because I believed I had a need for using |
Yup, for sure. We’re having a discussion internally about whether they’re
actively harmful. Right now it feels like a “no”, but curious what others
think.
…On Tue, Aug 28, 2018 at 4:59 PM Edmund ***@***.***> wrote:
For what it's worth, I subscribed to this thread because I believed I had
a need for using findDOMNode and was interested in updates. After
becoming more experienced and educated in React I was sure enough able to
use callback refs to achieve what I wanted.
https://stackoverflow.com/questions/51512130/what-is-the-best-way-to-call-an-external-function-on-the-dom-element-rendered-by
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#678 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AE029fXa7sOylrcSSSGKvBiZia4YuSr0ks5uVdlpgaJpZM4JKz4R>
.
|
The problem is when you need to access a DOM element nested inside a component exported by 3rd party library. Then you have absolutely no other option than to use |
@maulerjan That's not a problem. There are many times where linter rules are set to communicate that this is not the preferred style, and then require a disable comment. For example: handleClick = () => {
// NOTE: This library does not expose an API for getting a reference to its internal DOM node.
// See the issue I've opened at https://github.com/some-person/third-party-component/issues/64
// eslint:disable-next-line:react/no-find-dom-node
const element = ReactDOM.findDomNode(this.thirdPartyComponentRef.current)
alert(element.tagName)
} |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Just to add my use case for I realize this is library interop and not React's fault, but I've already torn my hair out over this one too long and |
Sorry scratch that it was my mistake... just refactored my code completely to be off findDOMNode! |
eslint error && findDOMNode
https://stackoverflow.com/questions/40499267/react-dnd-avoid-using-finddomnode React Hooks bugbug |
* Update eslint-plugin-react and enable new rules New rules: * `react/no-danger-with-children` (jsx-eslint/eslint-plugin-react#710) * `react/no-find-dom-node` (jsx-eslint/eslint-plugin-react#678) * `react/style-prop-object` (jsx-eslint/eslint-plugin-react#715) * Remove react/no-find-dom-node for now
* Update eslint-plugin-react and enable new rules New rules: * `react/no-danger-with-children` (jsx-eslint/eslint-plugin-react#710) * `react/no-find-dom-node` (jsx-eslint/eslint-plugin-react#678) * `react/style-prop-object` (jsx-eslint/eslint-plugin-react#715) * Remove react/no-find-dom-node for now
* Update eslint-plugin-react and enable new rules New rules: * `react/no-danger-with-children` (jsx-eslint/eslint-plugin-react#710) * `react/no-find-dom-node` (jsx-eslint/eslint-plugin-react#678) * `react/style-prop-object` (jsx-eslint/eslint-plugin-react#715) * Remove react/no-find-dom-node for now
* Update eslint-plugin-react and enable new rules New rules: * `react/no-danger-with-children` (jsx-eslint/eslint-plugin-react#710) * `react/no-find-dom-node` (jsx-eslint/eslint-plugin-react#678) * `react/style-prop-object` (jsx-eslint/eslint-plugin-react#715) * Remove react/no-find-dom-node for now
There are almost no situations where you’d want to use
findDOMNode()
over callback refs. We want to deprecate it eventually (not right now) because it blocks certain improvements in React in the future.For now, we think establishing a lint rule against it would be a good start. Here’s a few examples of refactoring
findDOMNode()
to better patterns.findDOMNode(this)
Before:
After:
findDOMNode(stringDOMRef)
Before:
After:
findDOMNode(childComponentStringRef)
Before:
After:
Other cases?
There might be situations where it’s hard to get rid of
findDOMNode()
. This might indicate a problem in the abstraction you chose, but we’d like to hear about them and try to suggest alternative patterns.The text was updated successfully, but these errors were encountered: