138

I am trying to use useRef with TypeScript but am having some trouble.

With my RefObject (I assume) I need to access current. (ie node.current)

I have tried the following

  • const node: RefObject<HTMLElement> = useRef(null);
  • const node = useRef<HTMLElement | null>(null);

but when I go to set the ref I am always told that X is not assignable to type 'LegacyRef<HTMLDivElement> | undefined'.

return <div ref={ node }>{ children }</div>

Edit: this should not be restricted to any one type of element so not just HTMLDivElement | HTMLFormElement | HTMLInputElement

Edit: This should work as an example

import React, { useRef, RefObject } from 'react';

function Test() 
{
    // const node = useRef(null);
    // const node: RefObject<HTMLElement> = useRef(null);
    const node = useRef<HTMLElement | null>(null);

    if (
        node &&
        node.current &&
        node.current.contains()
    ){ console.log("current accessed")}

    return <div ref={ node }></div>
}
FamousAv8er
  • 2,345
  • 2
  • 9
  • 27

10 Answers10

179

None of the above worked for me, but turns out the solution was quite simple...

All I was doing wrong was not explicitly including "null" as the parameter in the useRef initialization (it expects null, not undefined). Also you CANNOT use "HTMLElement" as your ref type, you have to be more specific, so for me it was "HTMLDivElement" for example).

So working code for me was something like this:

const ref = useRef<HTMLDivElement>(null);

return <div ref={ref}> Some Content... </div>
Sheldonfrith
  • 2,107
  • 2
  • 12
  • 16
  • 1
    Since I am using `createRef` rather than `useRef` I cannot initiate it with `null`, but I added it in the union of the possible types my ref can be (since my component can render one of a handful of sub-components). This did the trick. It ended up something like this: `private inputRef = React.createRef()` – schonarth Dec 27 '22 at 20:32
  • setting null as the default was the trick. Thank you. – Nish Feb 26 '23 at 22:33
  • Great as this is, my use case requires an array of refs, I'm not sure how to type for this. When I type null I continue to get the error... – Lauro235 Aug 31 '23 at 19:39
58

Just import React:

import React, { useRef } from 'react';

function Test() {
    const node = useRef<HTMLDivElement>(null);

    if (
        node &&
        node.current &&
        node.current.contains()
    ){ console.log("current accessed")}

    return <div ref={node}></div>
}

I made an update. Use HTMLDivElement as generic parameter instead of HTMLElement | null. Also, contains expects an argument.

UPDATE useRef expects generic argument of DOM element type. You don't need to use | null because RefObject already knows that current might be null.

See next type:

interface RefObject<T> {
  readonly current: T | null
}

TS & React are smart enough to figure out that your ref might be null

Ya Zhuang
  • 4,652
  • 31
  • 40
  • I appreciate the answer and this does work with the example I provided but unfortunately I need to add to my example. My apologies – FamousAv8er Apr 06 '21 at 06:32
  • I have modified the example if you wish to modify your answer – FamousAv8er Apr 06 '21 at 06:35
  • 3
    thank you this does work. If you could, please provide additional details as to *why* this works and why one should use this interface. thank you – FamousAv8er Apr 06 '21 at 07:14
  • 2
    Thanks for the update and I will accept this answer. I think I understand the part about `null` I am a bit more interested as to why `HTMLDivElement` is able to handle the `ref` but `HTMLElement` is not – FamousAv8er Apr 06 '21 at 07:28
  • 3
    HTMLElement interface is a parent interface for all html elements. It does not work because you are unable to create HTMLElement literally. You can only create HTMLDivElement, HTMLSpanElement, HTMLAnchorElement etc .... So React/JSX only can accept HTML{element}Element. For instance, it complains that HTMLElement does not have `allign` property. Hover mouse on ref attribute in your IDE press Ctrl and click on ref attribute. It should redirect you to ref types – captain-yossarian from Ukraine Apr 06 '21 at 07:33
  • @captain-yossarianfromUkraine that doesn't make sense. Isn't it supposed to be doing something like this `const htmlElement: HTMLElement = new HTMLDivElement();` underneath? This is essentially polymorphism, so why is useRef behaving differently? – George Aug 31 '23 at 17:04
  • @George you need to provide explicit generic because you use `null` as an initial value – captain-yossarian from Ukraine Aug 31 '23 at 20:38
  • @captain-yossarianfromUkraine it still doesn't make sense to me `let element: HTMLElement | null = null; element = new HTMLDivElement()`. This is valid no? – George Sep 01 '23 at 00:32
  • @George but rhis is not how useRef works – captain-yossarian from Ukraine Sep 01 '23 at 16:10
5

I came here looking for help with an iframe ref. Perhaps this solution will help someone else that's looking for the same thing. I replaced HTMLDivElement with HTMLIFrameElement so:

const node = useRef<HTMLIFrameElement>(null);
Brady Dowling
  • 4,920
  • 3
  • 32
  • 62
4
selfref = React.createRef<HTMLInputElement>()

I give the exacted TS Type, then the editor passed the check. it works nice now.

HduSy
  • 41
  • 2
3

The same stands for the <svg> elements:

const ref = useRef<SVGSVGElement>(null)
...
<svg ref={ref} />
Comi9
  • 69
  • 1
  • 5
2

After looking at all answers here I am not sure that any of them answer/explain the problem fully. In this case problem is two fold:

  • we need to use a different HTML interface object -> HTMLDivElement
  • we need to pass a default value to the reference initialization

React useRef function actually has 3 different signatures (in React 16.8):

If the passed default value is the same type as the generic type

function useRef\<T>(initialValue: T): MutableRefObject<T>;

If we pass a default value of the same type as the generic type OR null (as spotted by many responses here)

function useRef\<T>(initialValue: T|null): RefObject<T>;

If we pass no default values AND/OR we pass no generic type

function useRef\<T = undefined>(): MutableRefObject<T | undefined>;

The real issue comes down to incompatibility of MutableRefObject and LegacyRef. I am guessing React team did changes to the data types of references and therefor we have multiple signatures and types of references based on what we pass.

It would be nice to get a nicer error message from react eslint at least as the generic TS error message we get isn't enough to immediately spot what's wrong.

Tl;dr: passing null as the default value in useRef object will give us back the reference object that both satisfies MutableRefObject and LegacyRef (probably)

Filip Malek
  • 151
  • 1
  • 4
1

Key is to use HTMLElement and undefined for initialization

const node = useRef<HTMLElement>();
a2best
  • 182
  • 1
  • 3
0

As a workaround you can explicitly cast:

const ref = useRef<HTMLDivElement>();
...
<input
      ref={ref as RefObject<HTMLInputElement>}
      ...
/>
Artem
  • 79
  • 9
0

The reason why you're getting this error in typescript is because you need to type the ref.current and not just the ref

example:

const ref = React.useRef<HTMLDivElement>(null);

Component(props: {ref?: {current: HTMLDivElement}})
Plvtinum
  • 187
  • 1
  • 5
  • 11
0

I have the same error and there was a suggestion to convert to unknown first:

ref={inputRef as unknown as RefObject<HTMLTextAreaElement>}
Daryl Wong
  • 2,023
  • 5
  • 28
  • 61