61

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.

4 Answers 4

94

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>
    );
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much for such a very detail example.
4

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

Comments

2

The problem with useRef<typeof SomeForwardRefComponent> is, it thinks the ref.current will get the object type returned by forwardRef. It's not smart enough to look up the ref type. As a result, it expects properties like ref.current.displayName not ref.current.focus() etc.

You can extract the type you passed to the forwardRef's ref argument using React's ElementRef type tool, like useRef<ElementRef<typeof Child>>:

import React, { type ElementRef, type Ref, forwardRef, useRef } from 'react'

const Child = forwardRef<SomeElementType, Props>((props, ref) => (
  /* ...render something */
))
// ...or (same thing but with slightly different syntax where you specify ref type):
const Child = forwardRef((props: Props, ref: Ref<SomeElementType>) => (
  /* ...render something */
))

const Parent = () => {
  // ref here has the same type as if you did useRef<SomeElementType>
  const ref = useRef<ElementRef<typeof Child>>(null)
  return (
    <Child ref={ref} ... />
  )
}

This works, but ElementRef<typeof Child> is a bit long-winded. It's basically just a complicated alias for whatever you passed as a type parameter to forwardRef.

This may be the best solution if you're importing the forwardRefed component from a library you don't control, but if you control the code of both parent and child, you're probably better off just defining the ref type then exporting and importing it. For example:

// In `child.tsx`
import React, { forwardRef } from 'react'

export type ChildRefType = SomeElementType
export type ChildProps = { ... }

export const Child = forwardRef<ChildRefType, ChildProps>((props, ref) => (
  /* render something */
))
// In `parent.tsx`
import React, { useRef } from 'react'
import { Child, ChildRefType } from './child'

export const Parent = () => {
  // ref here has the same type as if you did useRef<SomeElementType>
  const ref = useRef<ChildRefType>(null)
  return (
    <Child ref={ref} ... />
  )
}

Comments

1

You can use ComponentRef<T extends ElementType> utility type to infer the ref's type of Child component.

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

const Parent = () => {
    const ref = useRef<ComponentRef<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>
    );
}

Output type:

enter image description here

TS Playground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.