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

🧠 Test Your Knowledge

What is the second parameter in a forwardRef component?