Definitelytyped: [@types/react] useRef return type clashes with ref prop type

Created on 19 May 2019  ·  12Comments  ·  Source: DefinitelyTyped/DefinitelyTyped

  • [x] I tried using the @types/16.8.17 package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [x] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
Type 'MutableRefObject<HTMLDivElement | undefined>' is not assignable to type 'string | ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null | undefined'.
  Type 'MutableRefObject<HTMLDivElement | undefined>' is not assignable to type 'RefObject<HTMLDivElement>'.
    Types of property 'current' are incompatible.
      Type 'HTMLDivElement | undefined' is not assignable to type 'HTMLDivElement | null'.
        Type 'undefined' is not assignable to type 'HTMLDivElement | null'.

Most helpful comment

A slightly neater solution is to set the initialValue to null in the useRef function call

const componentRef = useRef<HTMLDivElement>(null);

this ensures that componentRef = HTMLDivElement | null which is what the ref prop is expecting rather than HTMLDivElement | undefined.

All 12 comments

EDIT 2: Disregard my solution, use the one below :)

I currently have the same problem, have you found a solution already by any chance?

EDIT: I'm now using this, admittedly not so pretty, solution:

const inputField = React.useRef() as React.MutableRefObject<HTMLInputElement>;

Found here: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884#issuecomment-471341041

A slightly neater solution is to set the initialValue to null in the useRef function call

const componentRef = useRef<HTMLDivElement>(null);

this ensures that componentRef = HTMLDivElement | null which is what the ref prop is expecting rather than HTMLDivElement | undefined.

@shane935 you have made my day sir 🙌🏻

Seems to me undefined should be added to RefObject<T>.current like:

//index.ts, line 80
interface RefObject<T> {
   readonly current: T | null;
}

so it reads

//index.ts, line 80
interface RefObject<T> {
   readonly current: T | null | undefined;
}

It's not a bug, it's catching a legitimate typing error.

For more details, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/38228#issuecomment-529749802

Just as @Jessidhia say, it's not a bug , maybe you should write like this.

import React, {
  forwardRef,
  ForwardRefRenderFunction,
  useImperativeHandle,
  useRef,
  PropsWithChildren,
} from "react";

interface Props {}
interface Ref {
  value: string;
  setValue: (value: string) => void;
}

const InputEmail: ForwardRefRenderFunction<Ref, PropsWithChildren<Props>> = (
  props,
  ref
) => {
  // hooks
  const inputElement = useRef<HTMLInputElement | null>(null);
  useImperativeHandle(ref, () => {
    return {
      value: inputElement.current ? inputElement.current.value : "",
      setValue: (value: string) => {
        inputElement.current && (inputElement.current.value = value);
      },
    };
  });

  // render
  return (
    <div>
      <input
        type="text"
        ref={inputElement}
        defaultValue="[email protected]"
        disabled
      />
    </div>
  );
};

const Component = forwardRef(InputEmail);

export default Component;

I'm not sure this will fix everyone's problem, and I'm no expert on this stuff, but after looking at the typings of what I was passing in as a ref and the actual ref type of the component I was passing the ref into, it seemed as though I had the interfaces wrong.

I initially had an interface of props like so for the receiving component:

type TextboxProps = CustomProps & React.HTMLProps<HTMLInputElement>

which I changed to:

type TextboxProps = CustomProps & React.HTMLAttributes<HTMLInputElement>

the typing error then disappeared.

I'm not going to pretend I know more than I do, but looking into the typings for the HTMLProps interface and the React.forwardRef() function type you can see that it is using RefAttributes. Just above that, you can see the ClassAttributes type that HTMLProps is extending, of which defines the LegacyRef type which I believe was causing my typing issue.

It looks like this project has 3 overloads for useRef(), do we need all 3? It seems like the useRef<T>(null) case is the one developers will use most of the time.

    function useRef<T>(initialValue: T): MutableRefObject<T>;
    function useRef<T>(initialValue: T|null): RefObject<T>;
    function useRef<T = undefined>(): MutableRefObject<T | undefined>;
  1. What is the expect use case of the two mutable overloads?
  2. Is there some other way this can be supported to that users can more easily find the "correct" overload?

I have RefObject:

Screen Shot 2020-08-23 at 1 06 16 PM

I pass it to my Panel component as the prop ref:

Screen Shot 2020-08-23 at 1 06 04 PM

I try to consume w/ forwardRef, it becomes MutableRefObject:

Screen Shot 2020-08-23 at 1 06 56 PM

I'm now unable to consume the .current property:

Screen Shot 2020-08-23 at 1 10 27 PM

@Jessidhia @osf2e your suggestion does not seem applicable here.

The workaround I've had to resort to:

        if (ref && (ref as RefObject<HTMLDivElement>).current) {
              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
              (ref as RefObject<
                HTMLDivElement
              >)!.current!.style.height = `${containerHeight}px`;
            }

Using react-native-web, I had to use.

import {RefObject} from 'react';
// ...
const myRef = useRef() as RefObject<View>

I had this problem with a Ref for an Input element
So i switched from useRef<HTMLDivElement>() -> useRef<HTMLInputElement>() and it worked OK

@joshribakoff You can also type the parameters directly

export const Panel = React.forwardRef((props: PanelProps, ref: React.RefObject<HTMLDivElement>) => {

That way you shouldn't have to typecast when accessing your ref.
But I still don't understand why we have to do this...

Was this page helpful?
0 / 5 - 0 ratings