findDOMNode
findDOMNode is deprecated and will be removed in a future version of React. Use refs instead.
Returns the browser DOM node corresponding to a React component instance.
import { findDOMNode } from 'react-dom';
const domNode = findDOMNode(componentInstance);
Reference
findDOMNode(componentInstance)
Call findDOMNode to find the browser DOM node for a given React component instance.
/src/client/ReactDOMClient.js:43-47
const domNode = findDOMNode(this);
Parameters
An instance of a React component (typically this from a class component).
Returns
The browser DOM node corresponding to the component. Returns null if the component rendered null or false.
Why Is It Deprecated?
findDOMNode has several problems:
- Breaks abstraction - Reaches into component internals
- Only works with class components - Not compatible with function components
- Returns first child only - Confusing with fragments
- Prevents optimizations - React can’t optimize as much
- Breaks with Strict Mode - Causes issues in concurrent rendering
Migration Timeline
- React 16.3:
findDOMNode deprecated
- React 17: Warning in Strict Mode
- React 18: Stronger warnings
- Future: Will be removed entirely
Migration Guide
Pattern 1: Forwarding Refs
Before (with findDOMNode):
import { findDOMNode } from 'react-dom';
class MyInput extends React.Component {
focus() {
const node = findDOMNode(this);
if (node) {
node.focus();
}
}
render() {
return <input type="text" />;
}
}
// Usage
class Form extends React.Component {
inputRef = React.createRef();
handleSubmit = () => {
this.inputRef.current.focus();
};
render() {
return (
<form>
<MyInput ref={this.inputRef} />
<button onClick={this.handleSubmit}>Submit</button>
</form>
);
}
}
After (with forwardRef):
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
}
}));
return <input ref={inputRef} type="text" />;
});
// Usage
function Form() {
const inputRef = useRef(null);
const handleSubmit = () => {
inputRef.current?.focus();
};
return (
<form>
<MyInput ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</form>
);
}
Pattern 2: Direct Refs
Before (with findDOMNode):
import { findDOMNode } from 'react-dom';
class ImageGallery extends React.Component {
componentDidMount() {
const node = findDOMNode(this);
if (node instanceof Element) {
this.observer = new IntersectionObserver(entries => {
// Handle visibility
});
this.observer.observe(node);
}
}
componentWillUnmount() {
this.observer?.disconnect();
}
render() {
return <div className="gallery">{/* images */}</div>;
}
}
After (with ref):
import { useRef, useEffect } from 'react';
function ImageGallery() {
const galleryRef = useRef(null);
useEffect(() => {
const node = galleryRef.current;
if (!node) return;
const observer = new IntersectionObserver(entries => {
// Handle visibility
});
observer.observe(node);
return () => {
observer.disconnect();
};
}, []);
return <div ref={galleryRef} className="gallery">{/* images */}</div>;
}
Pattern 3: Measuring Elements
Before (with findDOMNode):
import { findDOMNode } from 'react-dom';
class Tooltip extends React.Component {
state = { width: 0 };
componentDidMount() {
const node = findDOMNode(this);
if (node instanceof Element) {
this.setState({ width: node.offsetWidth });
}
}
render() {
return (
<div className="tooltip">
Tooltip (width: {this.state.width}px)
</div>
);
}
}
After (with ref and ResizeObserver):
import { useRef, useEffect, useState } from 'react';
function Tooltip() {
const tooltipRef = useRef(null);
const [width, setWidth] = useState(0);
useEffect(() => {
const node = tooltipRef.current;
if (!node) return;
// Initial measurement
setWidth(node.offsetWidth);
// Watch for size changes
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
setWidth(entry.contentRect.width);
}
});
observer.observe(node);
return () => {
observer.disconnect();
};
}, []);
return (
<div ref={tooltipRef} className="tooltip">
Tooltip (width: {width}px)
</div>
);
}
Pattern 4: Event Listeners
Before (with findDOMNode):
import { findDOMNode } from 'react-dom';
class ClickOutside extends React.Component {
componentDidMount() {
document.addEventListener('click', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside);
}
handleClickOutside = (event) => {
const node = findDOMNode(this);
if (node && !node.contains(event.target)) {
this.props.onClickOutside?.();
}
};
render() {
return <div>{this.props.children}</div>;
}
}
After (with ref):
import { useRef, useEffect } from 'react';
function ClickOutside({ children, onClickOutside }) {
const containerRef = useRef(null);
useEffect(() => {
function handleClickOutside(event) {
if (containerRef.current && !containerRef.current.contains(event.target)) {
onClickOutside?.();
}
}
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('click', handleClickOutside);
};
}, [onClickOutside]);
return <div ref={containerRef}>{children}</div>;
}
Pattern 5: Third-Party Library Integration
Before (with findDOMNode):
import { findDOMNode } from 'react-dom';
import Picker from 'some-picker-library';
class DatePicker extends React.Component {
componentDidMount() {
const node = findDOMNode(this);
this.picker = new Picker(node, this.props.options);
}
componentWillUnmount() {
this.picker?.destroy();
}
render() {
return <input type="text" />;
}
}
After (with ref):
import { useRef, useEffect } from 'react';
import Picker from 'some-picker-library';
function DatePicker({ options }) {
const inputRef = useRef(null);
const pickerRef = useRef(null);
useEffect(() => {
if (!inputRef.current) return;
pickerRef.current = new Picker(inputRef.current, options);
return () => {
pickerRef.current?.destroy();
};
}, [options]);
return <input ref={inputRef} type="text" />;
}
Common Scenarios
Focus Management
// Modern approach
import { useRef } from 'react';
function SearchBox() {
const inputRef = useRef(null);
const handleSearch = () => {
inputRef.current?.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={handleSearch}>Search</button>
</>
);
}
import { useRef } from 'react';
function CommentList({ comments }) {
const lastCommentRef = useRef(null);
const scrollToLast = () => {
lastCommentRef.current?.scrollIntoView({ behavior: 'smooth' });
};
return (
<div>
{comments.map((comment, i) => (
<div
key={comment.id}
ref={i === comments.length - 1 ? lastCommentRef : null}
>
{comment.text}
</div>
))}
<button onClick={scrollToLast}>Scroll to Last</button>
</div>
);
}
Animation
import { useRef, useEffect } from 'react';
function FadeIn({ children }) {
const containerRef = useRef(null);
useEffect(() => {
const node = containerRef.current;
if (!node) return;
node.style.opacity = '0';
node.style.transition = 'opacity 300ms';
// Trigger animation
requestAnimationFrame(() => {
node.style.opacity = '1';
});
}, []);
return <div ref={containerRef}>{children}</div>;
}
TypeScript
import { useRef, useEffect } from 'react';
function MyComponent() {
// Type the ref appropriately
const divRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
// TypeScript knows the exact type
if (divRef.current) {
const width: number = divRef.current.offsetWidth;
}
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<div ref={divRef}>Content</div>
<input ref={inputRef} type="text" />
<button ref={buttonRef}>Click</button>
</>
);
}
Testing
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
function Button({ onClick }) {
const buttonRef = useRef(null);
return (
<button ref={buttonRef} onClick={onClick}>
Click me
</button>
);
}
test('button is clickable', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick} />);
const button = screen.getByRole('button');
await userEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
When You Might Still See It
You might encounter findDOMNode in:
- Legacy codebases - Old React code not yet migrated
- Third-party libraries - Older libraries still using it
- React Native - Different implementation, not deprecated there
Best Practices
- Use refs instead - Modern, performant, and future-proof
- Forward refs when needed - For component composition
- Use useImperativeHandle - For exposing imperative methods
- Avoid DOM manipulation - Let React manage the DOM
- Migrate legacy code - Update
findDOMNode usage proactively