React Forward Ref
Pass refs through components to child elements
🔗 What is Forward Ref?
forwardRef lets parent components access DOM nodes or component instances of their children. It's essential for reusable component libraries, allowing refs to pass through wrapper components to reach the actual DOM elements inside.
import { forwardRef } from 'react';
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
Key Forward Ref Concepts
forwardRef
Wrap component to accept refs
forwardRef(
(props, ref) => {...}
)
Ref Parameter
Second parameter receives the ref
(props, ref) => {
return <div ref={ref} />
}
useImperativeHandle
Customize exposed ref value
useImperativeHandle(
ref, () => ({...})
)
Use Cases
Input focus, scroll, animations
// Focus input
inputRef.current.focus()
🔹 Basic Forward Ref Example
Forward refs enable parent components to directly interact with child DOM elements. This is particularly useful for custom input components where you need to programmatically focus, select text, or access native DOM methods.
// CustomInput.jsx
import { forwardRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
return (
<input
ref={ref}
{...props}
style={{
padding: '10px',
border: '2px solid #3498db',
borderRadius: '4px',
fontSize: '16px'
}}
/>
);
});
export default CustomInput;
// App.jsx
import { useRef } from 'react';
import CustomInput from './CustomInput';
function App() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
return (
<div>
<CustomInput ref={inputRef} placeholder="Type here..." />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}
export default App;
🔹 Focus Management
One of the most common uses of forwardRef is managing input focus. This pattern is essential for accessibility, form validation feedback, and creating smooth user experiences with programmatic focus control.
// FancyInput.jsx
import { forwardRef } from 'react';
const FancyInput = forwardRef(({ label, ...props }, ref) => {
return (
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px' }}>
{label}
</label>
<input
ref={ref}
{...props}
style={{
width: '100%',
padding: '10px',
border: '1px solid #ddd',
borderRadius: '4px'
}}
/>
</div>
);
});
export default FancyInput;
// LoginForm.jsx
import { useRef } from 'react';
import FancyInput from './FancyInput';
function LoginForm() {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// Validate and focus on error
if (!emailRef.current.value) {
emailRef.current.focus();
return;
}
if (!passwordRef.current.value) {
passwordRef.current.focus();
return;
}
// Submit form
};
return (
<form onSubmit={handleSubmit}>
<FancyInput
ref={emailRef}
label="Email"
type="email"
placeholder="Enter email"
/>
<FancyInput
ref={passwordRef}
label="Password"
type="password"
placeholder="Enter password"
/>
<button type="submit">Login</button>
</form>
);
}
🔹 Scroll to Element
ForwardRef enables smooth scrolling to specific elements, useful for navigation, form validation errors, or creating single-page applications with anchor-like behavior. Access scrollIntoView and other DOM methods through the forwarded ref.
// Section.jsx
import { forwardRef } from 'react';
const Section = forwardRef(({ title, children }, ref) => {
return (
<section
ref={ref}
style={{
padding: '40px',
marginBottom: '20px',
background: '#f5f5f5',
borderRadius: '8px'
}}
>
<h2>{title}</h2>
{children}
</section>
);
});
export default Section;
// App.jsx
import { useRef } from 'react';
import Section from './Section';
function App() {
const section1Ref = useRef(null);
const section2Ref = useRef(null);
const section3Ref = useRef(null);
const scrollToSection = (ref) => {
ref.current.scrollIntoView({ behavior: 'smooth' });
};
return (
<div>
<nav style={{ position: 'sticky', top: 0, background: 'white' }}>
<button onClick={() => scrollToSection(section1Ref)}>
Section 1
</button>
<button onClick={() => scrollToSection(section2Ref)}>
Section 2
</button>
<button onClick={() => scrollToSection(section3Ref)}>
Section 3
</button>
</nav>
<Section ref={section1Ref} title="Section 1">
<p>Content for section 1</p>
</Section>
<Section ref={section2Ref} title="Section 2">
<p>Content for section 2</p>
</Section>
<Section ref={section3Ref} title="Section 3">
<p>Content for section 3</p>
</Section>
</div>
);
}
🔹 useImperativeHandle Hook
useImperativeHandle customizes the value exposed through refs, letting you control which methods and properties parent components can access. This creates cleaner APIs for component libraries by exposing only necessary functionality.
// VideoPlayer.jsx
import { forwardRef, useRef, useImperativeHandle } from 'react';
const VideoPlayer = forwardRef((props, ref) => {
const videoRef = useRef(null);
// Expose custom methods to parent
useImperativeHandle(ref, () => ({
play: () => {
videoRef.current.play();
},
pause: () => {
videoRef.current.pause();
},
reset: () => {
videoRef.current.currentTime = 0;
videoRef.current.pause();
}
}));
return (
<video
ref={videoRef}
width="400"
controls
style={{ borderRadius: '8px' }}
>
<source src={props.src} type="video/mp4" />
</video>
);
});
export default VideoPlayer;
// App.jsx
import { useRef } from 'react';
import VideoPlayer from './VideoPlayer';
function App() {
const playerRef = useRef(null);
return (
<div>
<VideoPlayer ref={playerRef} src="video.mp4" />
<div style={{ marginTop: '10px' }}>
<button onClick={() => playerRef.current.play()}>
Play
</button>
<button onClick={() => playerRef.current.pause()}>
Pause
</button>
<button onClick={() => playerRef.current.reset()}>
Reset
</button>
</div>
</div>
);
}
🔹 Custom Modal with Ref
Create reusable modal components with imperative controls using forwardRef and useImperativeHandle. This pattern allows parent components to open, close, and control modals programmatically while keeping modal logic encapsulated.
// Modal.jsx
import { forwardRef, useImperativeHandle, useState } from 'react';
const Modal = forwardRef(({ title, children }, ref) => {
const [isOpen, setIsOpen] = useState(false);
useImperativeHandle(ref, () => ({
open: () => setIsOpen(true),
close: () => setIsOpen(false),
toggle: () => setIsOpen(!isOpen)
}));
if (!isOpen) return null;
return (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}>
<div style={{
background: 'white',
padding: '30px',
borderRadius: '8px',
maxWidth: '500px',
width: '90%'
}}>
<h2>{title}</h2>
{children}
<button onClick={() => setIsOpen(false)}>
Close
</button>
</div>
</div>
);
});
export default Modal;
// App.jsx
import { useRef } from 'react';
import Modal from './Modal';
function App() {
const modalRef = useRef(null);
return (
<div>
<button onClick={() => modalRef.current.open()}>
Open Modal
</button>
<Modal ref={modalRef} title="Welcome">
<p>This is a modal controlled by ref!</p>
</Modal>
</div>
);
}
🔹 Best Practices
Follow these guidelines when using forwardRef in your React applications:
- Use Sparingly: Only forward refs when necessary; prefer props for most cases
- Name Components: Give display names to forwardRef components for better debugging
- Document API: Clearly document what methods are exposed via useImperativeHandle
- Avoid Overuse: Don't expose too many methods; keep the API minimal
- Type Safety: Use TypeScript to type ref values for better developer experience
- Combine with Hooks: Use with useImperativeHandle for controlled exposure
🔹 Common Use Cases
ForwardRef is particularly useful in these scenarios:
✅ When to Use forwardRef:
- Form Inputs: Custom input components that need focus management
- Component Libraries: Reusable components that wrap native elements
- Animation Control: Components that need imperative animation triggers
- Scroll Management: Sections or containers that need scroll control
- Media Players: Video/audio components with playback controls
❌ When NOT to Use forwardRef:
- Simple presentational components without DOM interaction needs
- When props and callbacks can achieve the same result
- Components that don't need parent access to internal elements