React - useRef with TypeScript and functional component

Viewed 76643

I'm trying to call the child component method from the parent component using useRef.

In the future, the SayHi method will update the hook state in the child component. Unfortunately, I have bugs I can't deal with.

Line: ref.current.SayHi();

Property 'SayHi' does not exist on type 'ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>'.

Line: <Child name="Adam" ref={ref}/>

Type 'RefObject<ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>>' is not assignable to type '((instance: { SayHi: () => void; } | null) => void) | RefObject<{ SayHi: () => void; }> | null | undefined'. Type 'RefObject<ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>>' is not assignable to type 'RefObject<{ SayHi: () => void; }>'. Property 'SayHi' is missing in type 'ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>' but required in type '{ SayHi: () => void; }'.

Full test.tsx file:

import React, { useRef, forwardRef, useImperativeHandle, Ref } from 'react'

const Parent = () => {
  const ref = useRef<typeof Child>(null);
  const onButtonClick = () => {
    if (ref.current) {
      ref.current.SayHi();
    }
  };
  return (
    <div>
      <Child name="Adam" ref={ref}/>
      <button onClick={onButtonClick}>Log console</button>
    </div>
  );
}

const Child = forwardRef((props: {name: string}, ref: Ref<{SayHi: () => void}>)=> {
  const {name} = props;
  useImperativeHandle(ref, () => ({ SayHi }));
  
  function SayHi() { console.log("Hello " + name); }
  
  return <div>{name}</div>;
});

I deeply ask for help on this topic.

2 Answers

You require to extract the ref type elsewhere:

interface RefObject {
  SayHi: () => void
}

then just refer to it in both places

const Child = forwardRef((props: {name: string}, ref: Ref<RefObject>)=> {
  const {name} = props;  
  useImperativeHandle(ref, () => ({ SayHi }));
  function SayHi() { console.log("Hello " + name); }

  return <div>{name}</div>;
});
const Parent = () => {
    const ref = useRef<RefObject>(null);
    const onButtonClick = () => {
      if (ref.current) {
        ref.current.SayHi();
      }
    };
    return (
      <div>
        <Child name="Adam" ref={ref}/>
        <button onClick={onButtonClick}>Log console</button>
      </div>
    );
}

Just replace the declaration of your ref with this const ref = useRef<{ SayHi: () => void }>(null);

Related