Property 'current' does not exist on type '((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement>'
Asked Answered
D

3

54

Usually when I use React + Typescript and I have to work with refs I usually use this check:

const ref = useRef<HTMLDivElement>(null)
...
if(ref && ref.current)

But recently, I'm receiving this error:

Property 'current' does not exist on type '((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement>'.
  Property 'current' does not exist on type '(instance: HTMLDivElement | null) => void'.ts(2339)

Any ideas on what this error means? To solve the issue, as a workaround, I'm adding a third check:

if(ref && "current" in ref && ref.current)

But this looks pretty bad, mainly when you have to work with multiple refs at once.

Thanks for your help.

Ducky answered 24/1, 2021 at 22:43 Comment(3)
How was the ref created? with useRef, you always get a ref object, which is why your normal code works. But it looks like this code is expecting that it might receive a ref callback instead.Had
I'm creating the ref like this const cardRef = useRef<HTMLDivElement>(null);Ducky
That should not produce the issue you're describing. Do you redefine the type somewhere? Like say const ref: Ref<HtmlDivElement> = cardRef? Or pass it as a prop to a component which expects a Ref<HtmlDivElement>?Had
H
121

There are two kinds of refs in modern react: ref objects, and ref callbacks. Ref objects are what's created by useRef (or in class components, createRef): it's an object with a current property. In typescript, these have the type RefObject<T>, where T is whatever value(s) will be on current.

Ref callbacks are another option, which are needed for some advanced cases. You pass a function into the element, and the function will be called back when the instance is created or destroyed. These have the type (instance: T) => void.

A shorthand which combines both the ref object and the ref callback into a single type is Ref<T>, and it looks like that's what your code expects. Since you haven't shown that code, I'll have to make some educated guesses about what it looks like. Suppose you have a component which accepts a ref as a prop (perhaps so it can then hand it off to one of its internal components):

interface ExampleProps {
  buttonRef: Ref<HTMLButtonElement>
}

const Example: FC<ExampleProps> = ({ buttonRef }) => {
  return (
    <div>
      <button ref={buttonRef}>Hello</button>
    <div>
  )
}

Since I've defined the prop to be a Ref, it can be passed in either a ref object, or a ref callback. That's fine in this case, since I'm not doing anything with it except passing it on to the button. But if I try to write some code to interact with it, I can't assume it to be an object or a function.

If I need to do this, perhaps I could restrict the prop so it only takes ref objects, and then I can assume it will have .current

interface ExampleProps {
  buttonRef: RefObject<HTMLButtonElement>
}

const Example: FC<ExampleProps> = ({ buttonRef }) => {
  useEffect(() => {
    console.log(buttonRef.current);
  });
  return (
    <div>
      <button ref={buttonRef}>Hello</button>
    <div>
  )
}

But maybe I don't want to restrict the way my component can be used, and yet I still need to be able to interact with the ref. In that case, I'll need to make a callback ref of my own, and then add logic to it to handle both my use of the ref, and the prop's use of the ref:

interface ExampleProps {
  buttonRef: Ref<HTMLButtonElement>;
}

const Example: FC<ExampleProps> = ({ buttonRef }) => {
  const myRef = useRef<HTMLButtonElement | null>(null);
  useEffect(() => {
    console.log(myRef.current);
  });
  return (
    <div>
      <button
        ref={(element) => {
          myRef.current = element;
          if (typeof buttonRef === "function") {
            buttonRef(element);
          } else {
            buttonRef.current = element;
          }
        }}
      >
        Hello
      </button>
    </div>
  );
};
Had answered 24/1, 2021 at 23:55 Comment(3)
thank you very much for your great explanation! Could you tell me please, where you get all this information about Typescript with React? I am searching for some study resources, but I can't find anything comprehensive. Thank you for the hint.Alexandrine
This filled in the missing piece for me! You did a great job of explaining! This should be part of a react cookbook or in an article.Kortneykoruna
I just found out from your answer 99% of the web doesn't know when to use forwardRef and when to use useRefAssent
H
10

Following Nicholas Tower's answer, I did a reusable and typed util function to set both refs that might useful to others:

import { ForwardedRef, MutableRefObject } from 'react';

export const setComponentRefs =
 <T>(ref: MutableRefObject<T | null>, forwardedRef: ForwardedRef<T>) =>
 (el: T) => {
   ref.current = el;
   if (typeof forwardedRef === 'function') forwardedRef(el);
   else if (forwardedRef) forwardedRef.current = el;
};


///////// Example

<View ref={setComponentRefs(innerRef, ref)}>
 {...}
</View>
Hangdog answered 21/6, 2022 at 12:55 Comment(0)
T
10

If you want to access a ref while also forwarding it, it is simpler with the useImperativeHandle hook.

For example:

import { forwardRef, useImperativeHandle, useRef } from 'react';
const MyComponent = forwardRef<HTMLDivElement>((props, outerRef) => {
    const innerRef = useRef<HTMLDivElement>(null);
    useImperativeHandle(outerRef, () => innerRef.current!, []);
    // innerRef.current refers to the div element & can be used inside MyComponent
    // a ref can also be passed by the consumer to MyComponent to get access to the same element
    return <div ref={innerRef}>Some content...</div>;
});
Thomasinethomason answered 6/9, 2023 at 21:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.