Reputation: 627
I'm still a bit new to Typescript and my goal is to create a custom React.useRef
hook in Typescript that returns a ref. I would like this ref to be typed for any HTML element and not just buttons only. I'm running into issues of typing this custom hook properly.
Here's my hook:
type CustomRefHook = (dependencies?: string | boolean | number[]) => React.MutableRefObject<HTMLButtonElement | null>;
const useCustomRefHook: CustomRefHook = (...dependencies) => {
const ref = React.useRef<HTMLButtonElement | null>(null);
useEffect(() => {
// ... do stuff
}, dependencies)
return ref;
}
For now, the majority of my use cases for this hook would be for buttons but I would like to eventually expand this to any sort of HTML element. I've tried using HTMLElement
instead of HTMLButtonElement
, however I get a type error of:
Type 'MutableRefObject<HTMLElement | null>' is not assignable to type 'string | ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null | undefined'.
Type 'MutableRefObject<HTMLElement | null>' is not assignable to type 'RefObject<HTMLButtonElement>'.
Types of property 'current' are incompatible.
Type 'HTMLElement | null' is not assignable to type 'HTMLButtonElement | null'.
whenever I try to attach this ref to a button. Using HTMLButtonElement
quiets the Typescript errors, but again I'd like this hook to be able to handle any type of HTML element.
Upvotes: 5
Views: 5568
Reputation: 42188
The type of the ref
needs to exactly match the DOM element that it is attached to, so you need to use a generic to specify the element type.
Since useCustomRefHook
is now a generic function, it makes more sense to declare the typings inline rather than as a separate type CustomRefHook
.
I have allowed this hook to accept any
value of T
, but you could use T extends HTMLElement
if you just want it to apply to HTML elements. That might be important depending on the contents of "do stuff" inside your useEffect
. If "do stuff" requires anything specific about T
, then you need to make sure that T
extends something with the required properties/methods.
import React, { MutableRefObject, DependencyList, useEffect, useRef } from "react";
const useCustomRefHook = <T extends any>( dependencies: DependencyList = [] ): MutableRefObject<T | null> => {
const ref = useRef<T | null>(null);
useEffect(() => {
// ... do stuff
}, dependencies);
return ref;
};
The hook returns a MutableRefObject<T | null>
based on the generic T
. The value of T
cannot possibly be inferred from the arguments, so you will need to specify the generic every time that you call it.
const Test = () => {
const ref = useCustomRefHook<HTMLButtonElement>();
return <button ref={ref} />;
};
Upvotes: 9