5

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.

3
  • 1
    You will need to use a generic for the element type. Commented Jan 22, 2021 at 22:52
  • What would that look like? I've tried something like: type CustomRefHook = <T>(dependencies?: string | boolean | number[]) => React.MutableRefObject<T>; Commented Jan 22, 2021 at 22:54
  • 1
    Yeah that's basically it. I'll type up an answer. Commented Jan 22, 2021 at 22:55

1 Answer 1

9

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} />;
};
Sign up to request clarification or add additional context in comments.

3 Comments

Q: How would I extract this out into a type declaration? type CustomRefType = <T extends HTMLElement>(dependencies: React.DependencyList) => React.MutableRefObject<T | null> const useCustomRefHook: CustomRefType = (dependencies = []) => { const ref = useRef<T | null>(null); } I get an error saying "Cannot find name 'T'". How would I provide the generic type down to the React.useRef? @Linda Paiste
You can’t really extract it out when the function itself is generic.
Got it. Ok that makes sense

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.