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;
}
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
- Ref Removal: The
ref prop is removed from the props object passed to the render function
- Symbol Marking: Sets
$$typeof to Symbol.for('react.forward_ref') for React compatibility
- Display Name: Automatically generates a display name for debugging
- 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
- Type Safety: Always type your refs in TypeScript for better IDE support
- Display Names: Set a
displayName on your render function for better debugging
- Null Checks: Always check if the ref exists before using it
- 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