How do I type a ref callback with useCallback (with Typescript)?

Viewed 5299

I'm new to Typescript and can't figure out how to type this situation. I'm writing a custom hook and trying to create a callback ref. My problem is that this function sets the ref's current, and returns nothing, but since I use it as a ref, typescript yells at me Property 'current' is missing in type '(node: any) => void' but required in type 'RefObject<HTMLDivElement>'.

Thank you in advance.

This is the code:

import React, {useCallback, useRef} from 'react'

const useCustom = (): [RefObject<HTMLDivElement>] => {
  const ref = useRef<HTMLDivElement | null>(null)
  const setRef = useCallback(node => {
    ....
    ref.current = node
  }, [])

  return [setRef]
}

const SomeComp = () => {
  const [ref] = useCustom()

  return <div ref={ref}>Text</div>
}
2 Answers

The problem is you said the return value of useCustom would be RefObject<HTMLDivElement>, but returned (node: HTMLDivElement) => void.

Your custom hook should return 2 values: one for setting the ref value, the other for the ref itself. So it will look like useState hook:

const useCustom = (): [
  RefObject<HTMLDivElement>,
  (node: HTMLDivElement) => void
] => {
  const ref = useRef<HTMLDivElement | null>(null);
  const setRef = useCallback((node) => {
    ref.current = node;
  }, []);

  return [ref, setRef];
};

I propose a different solution.

This is assuming the author is trying to use a "callback ref" to execute a side effect, when the ref changes. When using two return values the ref might still accidentally be set (by using the ref) without executing the callback (setRef), which I'm guessing is not the author's intention.

Typing things like this seems to work as expected (using author's example):

import React, {useCallback, useRef} from 'react'

const useCustom = (): [React.RefCallback<HTMLElement>] => {
    const ref = useRef<HTMLElement | null>(null)
    const setRef: React.RefCallback<HTMLElement> = useCallback(node => {
    ....
    ref.current = node
  }, [])

  return [setRef]
}

const SomeComp = () => {
  const [ref] = useCustom()

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

Note: I also changed HTMLDivElement to HTMLElement to make the hook more universal.

Related