Skip to main content

forwardRef

Pass ref down to a child component. This is mainly used in libraries with higher-order components that wrap other components. Using forwardRef, there is an easy way to get a reference to the wrapped component instead of the wrapper itself.

Signature

function forwardRef<T = any, P = {}>(
  fn: ForwardRefRenderFunction<T, P>
): FunctionalComponent<PropsWithoutRef<P> & { ref?: Ref<T> }>
fn
ForwardRefRenderFunction<T, P>
required
A render function that receives props and a forwarded ref.
interface ForwardRefRenderFunction<T = any, P = {}> {
  (props: P, ref: ForwardedRef<T>): ComponentChild;
  displayName?: string;
}
returns
FunctionalComponent
A functional component that forwards refs to the inner component.

Usage

Basic Example

Forward a ref to a DOM element:
import { forwardRef } from 'preact/compat';

const FancyButton = forwardRef((props, ref) => (
  <button ref={ref} className="fancy-button">
    {props.children}
  </button>
));

// Usage
function App() {
  const buttonRef = useRef();
  
  return <FancyButton ref={buttonRef}>Click me!</FancyButton>;
}

With TypeScript

Type the ref and props correctly:
import { forwardRef, Ref } from 'preact/compat';

interface ButtonProps {
  variant: 'primary' | 'secondary';
  children: React.ReactNode;
}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant, children }, ref) => (
    <button ref={ref} className={`btn-${variant}`}>
      {children}
    </button>
  )
);

Higher-Order Component

Forward refs through a HOC:
import { forwardRef } from 'preact/compat';

function withLogging(Component) {
  const WithLogging = forwardRef((props, ref) => {
    console.log('Rendering:', Component.displayName || Component.name);
    return <Component {...props} ref={ref} />;
  });
  
  WithLogging.displayName = `withLogging(${Component.displayName || Component.name})`;
  return WithLogging;
}

Imperative Handle

Expose custom methods through the ref:
import { forwardRef, useImperativeHandle, useRef } from 'preact/compat';

const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    }
  }));
  
  return <input ref={inputRef} {...props} />;
});

// Usage
function Form() {
  const inputRef = useRef();
  
  const handleSubmit = () => {
    inputRef.current.clear();
  };
  
  return (
    <>
      <CustomInput ref={inputRef} />
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

Implementation Details

The forwardRef implementation in Preact:
export function forwardRef(fn) {
  function Forwarded(props) {
    let clone = assign({}, props);
    delete clone.ref;
    return fn(clone, props.ref || null);
  }

  // mobx-react checks for this being present
  Forwarded.$$typeof = REACT_FORWARD_SYMBOL;
  Forwarded.render = fn;
  Forwarded.prototype.isReactComponent = true;
  Forwarded.displayName = 'ForwardRef(' + (fn.displayName || fn.name) + ')';

  return Forwarded;
}

Key Features

  1. Ref Removal: The ref prop is removed from the props object passed to the render function
  2. Symbol Marking: Sets $$typeof to Symbol.for('react.forward_ref') for React compatibility
  3. Display Name: Automatically generates a display name for debugging
  4. MobX Compatibility: Includes special handling for mobx-react compatibility

ForwardedRef Type

The ref parameter can be:
type ForwardedRef<T> =
  | ((instance: T | null) => void)  // Callback ref
  | MutableRefObject<T | null>      // Ref object
  | null;                            // No ref

Best Practices

  1. Type Safety: Always type your refs in TypeScript for better IDE support
  2. Display Names: Set a displayName on your render function for better debugging
  3. Null Checks: Always check if the ref exists before using it
  4. Documentation: Document what the ref exposes when using useImperativeHandle

Common Patterns

Conditional Ref Forwarding

const ConditionalRef = forwardRef((props, ref) => {
  const localRef = useRef();
  const actualRef = ref || localRef;
  
  return <input ref={actualRef} {...props} />;
});

Multiple Refs

const MultiRef = forwardRef((props, ref) => {
  const internalRef = useRef();
  
  useEffect(() => {
    // Use internal ref for component logic
    internalRef.current.focus();
  }, []);
  
  return <input ref={(node) => {
    internalRef.current = node;
    if (typeof ref === 'function') {
      ref(node);
    } else if (ref) {
      ref.current = node;
    }
  }} {...props} />;
});

Source

Implementation: compat/src/forwardRef.js:1-32

Build docs developers (and LLMs) love