5

I'm struggling to understand why the typescript compiler is unhappy with the forwardRef I'm using in a component.

I've pared my code right down to the bare essentials for the example below, ensuring this typescript error is the only one that manifests in the code, as per the issue in my actual code:

import { createRef, forwardRef, RefObject } from 'react';

const MyComponent = () => {
    const selectCountryRef = createRef<HTMLSelectElement>();

    return (
        <div>
            <SelectCountry ref={selectCountryRef} />
        </div>
    );
};

const SelectCountry = forwardRef((ref?: RefObject<HTMLSelectElement>) => {
    return (
        <FormFieldSelect ref={ref} />
    );
});

const FormFieldSelect = ({ ref }) => {
    return (
        <div>
            <select ref={ref}>
            </select>
        </div>
    );
};

When trying to compile this, I get the error:

Property 'current' is missing in type '{ ref: RefObject<HTMLSelectElement>; }' but required in type 'RefObject<HTMLSelectElement>'.

In VSCode, <SelectCountry ... is where the IDE is having a whinge, the exact message is:

const SelectCountry: ForwardRefExoticComponent<RefObject<HTMLSelectElement> & RefAttributes<unknown>> Property 'current' is missing in type '{ ref: RefObject<HTMLSelectElement>; }' but required in type 'RefObject<HTMLSelectElement>'.ts(2741) index.d.ts(85, 18): 'current' is declared here.

I'm just failing to understand why I'm getting this error.

Currently using React 17.0.2

3 Answers 3

4

forwardRef gets called with the props argument first and the ref argument second.

Your ForwardRefRenderFunction is using ref?: RefObject<HTMLSelectElement> as the first argument which is the props. As far as TypeScript is concerned, the type of the props for SelectCountry is a RefObject<HTMLSelectElement>. That's why you get the error:

Property 'current' is missing in type { ref: RefObject<HTMLSelectElement>; } but required in type RefObject<HTMLSelectElement>.


@jeffreyquan's answer shows you a properly-typed solution so I won't reiterate everything that they wrote. You can assign types to the props and ref by setting the generic type parameters on the forwardRef function like they showed you.

You can also assign the types by annotating the props and ref arguments of the inner function like you were trying to do, but you need to make sure that it's done correctly. If you don't assign any type to props then it gets inferred as PropsWithChildren<{}> which is actually fine here. So this works:

const SelectCountry = forwardRef((props, ref: ForwardedRef<HTMLSelectElement>) => {
    return (
        <FormFieldSelect ref={ref} />
    );
});

In order to pass typechecking in strict mode you need to change RefObject to ForwardedRef. This allows for callback refs and null.

Sign up to request clarification or add additional context in comments.

Comments

2

Try the following. I've added a useEffect and a console.log in MyComponent to show that it's working. I was confused with how you assigned ref?: RefObject<HTMLSelectElement> in SelectCountry and believe this was causing the error. I've shown how to assign types to forwardRef in the code snippet i.e. forwardRef<RefElementType,ComponentPropsType>. The FormFieldSelect also must be a forwardRef component.

import React, { useEffect, createRef, forwardRef } from 'react'

export const MyComponent = () => {
  const selectCountryRef = createRef<HTMLSelectElement>()

  useEffect(() => {
    console.log(selectCountryRef.current)
  }, [selectCountryRef])

  return (
    <div>
      <SelectCountry ref={selectCountryRef} />
    </div>
  )
}

interface SelectCountryProps {}

const SelectCountry = forwardRef<HTMLSelectElement, SelectCountryProps>(
  (props, ref) => {
    return <FormFieldSelect ref={ref} />
  },
)

interface FormFieldSelectProps {}

const FormFieldSelect = forwardRef<HTMLSelectElement, FormFieldSelectProps>(
  (props, ref) => {
    return (
      <div>
        <select ref={ref}></select>
      </div>
    )
  },
)

2 Comments

Oh, interesting! - that's given me food for thought, thanks! My 'real world' example is a whole lot more complicated, obviously, but this adjustment of my basic code I posted, should give me the inspiration I need to solve the issue - looks like I need some refactoring. The mention of the FormFieldSelect needing to be a forwardRef looks like the solution, once I can figure it out.
You're welcome @MatthewTrow :) Glad I could help. Could you please upvote my answer if you found it useful? And if it my answer helped resolve the original question, could you please accept it the answer? Any of this would really help me. Thank you!
0

I realize this is an older question, but I ran into this recently and this is what worked for me. Note that, per React's documentation, createRef is meant to be used with class components. Instead you should use the hook useRef. Also refer to forwardRef for further helpful tips.

import { ChangeEventHandler, forwardRef, useRef, useState } from "react";

// This can be any type, but be sure to omit the `ref` prop if extending
// another interface/type
interface InputProps {
  value: string;
  onChange: (value: string) => void;
}

// Specify types on forwardRef instead of in the function
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
    props.onChange(event.target.value);
  };

  return (
    <input ref={ref} onChange={handleChange} value={props.value} type="text" />
  );
});

function App() {
  // 1. Use the hook `useRef`, not the function `createRef`
  // 2. Must match the first type passed to forwardRef
  // 3. Must have initial value set to `null`
  const inputRef = useRef<HTMLInputElement>(null);

  const [value, setValue] = useState("");

  return <Input ref={inputRef} value={value} onChange={setValue} />;
}

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.