Search code examples
reactjstypescriptreftsxreact-functional-component

React - useRef with TypeScript and functional component


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.


Solution

  • 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>
        );
    }