React - useRef and forwardRef
Overview
Estimated time: 20–30 minutes
Refs let you hold mutable values across renders and access DOM nodes. Forwarding refs makes library components ref-friendly. Use useImperativeHandle to expose safe imperative APIs.
Try it: Focus input with useRef
View source
function FocusDemo(){
  const inputRef = React.useRef(null);
  return (
    <div>
      <input ref={inputRef} placeholder="Click the button to focus me" />
      <button onClick={() => inputRef.current && inputRef.current.focus()} style={{marginLeft:8}}>Focus</button>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('try-ref')).render(<FocusDemo />);
Try it: forwardRef + useImperativeHandle
View source
const FancyInput = React.forwardRef(function FancyInput(props, ref){
  const innerRef = React.useRef(null);
  React.useImperativeHandle(ref, () => ({
    focus: () => innerRef.current && innerRef.current.focus(),
    value: () => innerRef.current ? innerRef.current.value : ''
  }));
  return <input ref={innerRef} {...props} />;
});
function Parent(){
  const api = React.useRef(null);
  return (
    <div>
      <FancyInput ref={api} placeholder="Forwarded ref input" />
      <button onClick={() => api.current && api.current.focus()} style={{marginLeft:8}}>Focus</button>
      <button onClick={() => alert(api.current ? api.current.value() : '')} style={{marginLeft:8}}>Get Value</button>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('try-forwardref')).render(<Parent />);
Syntax primer
- useRef(initial)stores a mutable- .currentthat persists across renders.
- forwardRefpasses a parent ref down to a child component.
- useImperativeHandlelimits the imperative API exposed by a ref for safety.
Common pitfalls
- Overusing refs for state—prefer state unless you must escape React’s render cycle.
- Exposing full DOM nodes unnecessarily—prefer a narrow imperative handle.
Checks for Understanding
- When should you prefer state over refs?
- Why use useImperativeHandleinstead of exposing a full DOM node?
Show answers
- When UI should update based on changes—state triggers re-renders; refs do not.
- To provide a minimal, safe imperative API and hide internal implementation details.
Exercises
- Create a Timerwith a ref exposingstart(),stop(), andreset().
- Expose a limited API from a complex input (e.g., clear(),focus()) usinguseImperativeHandle.
- Build a form that focuses the first invalid field using refs.