RefObject
interface RefObject<T> {
readonly current: T | null;
}
RefObject
is the return type of the React.createRef
method.
When this method is called, it returns an object with its only field .current
set to null
. Soon after, when a render
passes the ref to a component, React will set .current
to a reference to the component. This component will generally be a DOM element (if passed to a HTML element) or an instance of a class component (if passed to a custom class component).
Note that RefObject
is very similar to MutableRefObject<T | null>
, with the exception that .current
is readonly
. This type specification is only made to indicate that the .current
property is managed internally by React and should not be modified by the code in a React app.
MutableRefObject
interface MutableRefObject<T> {
current: T;
}
MutableRefObject
is the return type of the React.useRef
method. Internally, React.useRef
makes a MutableRefObject
, stores it to the state of the functional component, and returns the object.
Note that when objects are stored to the state of a React component, modifying their properties will not trigger a re-render (since Javascript objects are reference types). This situation allows you to mimic class instance variables in functional components, which don't have instances. In other words, you can think of React.useRef
as a way to associate a variable with a functional component without it affecting the component's renders.
Here's an example of a class component using instance variables and a functional component using React.useRef
to achieve the same purpose:
class ClassTimer extends React.Component {
interval: NodeJS.Timer | null = null;
componentDidMount() {
this.interval = setInterval(() => { /* ... */ });
}
componentWillUnmount() {
if (!this.interval) return;
clearInterval(this.interval);
}
/* ... */
}
function FunctionalTimer() {
const intervalRef = React.useRef<NodeJS.Timer>(null);
React.useEffect(() => {
intervalRef.current = setInterval(() => { /* ... */ });
return () => {
if (!intervalRef.current) return;
clearInterval(intervalRef.current);
};
}, []);
/* ... */
}
ForwardedRef
type ForwardedRef<T> =
| ((instance: T | null) => void)
| MutableRefObject<T | null>
| null;
ForwardedRef
is the type of ref React passes to functional components using React.forwardRef
.
The main idea here is that parent components can pass a ref down to child components. For example, MyForm
can forward a ref to MyTextInput
, allowing the former to access the .value
of the HTMLInputElement
that the latter renders.
Breaking down the union type:
MutableRefObject<T | null>
- The forwarded ref was created with React.useRef
.
((instance: T | null) => void)
- The forwarded ref is a callback ref.
null
- No ref was forwarded.
Using ForwardedRef in the child component
When a child component receives a ForwardedRef
, it is often to expose the ref to a parent. However, sometimes the child component may need to use the ref itself. In this case, you can use a hook to the reconcile each of the ForwardedRef
types listed above.
Here is a hook from this article (adjusted for Typescript) that helps achieve this:
function useForwardedRef<T>(ref: React.ForwardedRef<T>) {
const innerRef = React.useRef<T>(null);
React.useEffect(() => {
if (!ref) return;
if (typeof ref === 'function') {
ref(innerRef.current);
} else {
ref.current = innerRef.current;
}
});
return innerRef;
}
The idea behind this hook is that the component can create its own ref, which it can use regardless of whether the parent forwarded a ref. The hook helps ensure that any forwarded ref's .current
property is kept in sync with the inner one's.
The return type of this hook is MutableRefObject<T>
, which should be compatible with the RefObject<T>
argument in your code snippet for useScrollUtils
, e.g.:
const MyComponent = React.forwardRef<HTMLDivElement>(
function MyComponent(_props, ref) {
const innerRef = useForwardedRef(ref);
useScrollUtils(innerRef);
return <div ref={innerRef}></div>;
}
);