Skip to content

React: Type inference not working for ref callback #16019

Description

Type inference is not working for the ref callback prop.
I believe this is the same issue as in #7088, which was fixed over a year ago.

TypeScript Version: 2.3.2

Code

// A *self-contained* demonstration of the problem follows...
class Other extends React.Component<any, any> {
	render() {
		return <div />;
	}
}

class Container extends React.Component<any, any> {
	private _ref: Other;

	render() {
                // Parameter 'ref' implicitly has an 'any' type.
		return <Other ref={ ref => this._ref = ref } />;
	}
}

Expected behavior:
Type of parameter ref should be inferred as Test

Actual behavior:
Parameter 'ref' implicitly has an 'any' type.

Activity

  1. mhegazy commented on May 24, 2017

    @mhegazy
    Contributor

    The issue here is that the type of props is any, since Other is declared to extend from React.Component<any, any>. The result here is that all references to props are practically unchecked.

    if this component does not have any properties, then declare it as extends React.Component<{}, any>.

    if the intention is to allow any property to be defined on this component, then declare at as extends React.Component<Record<string, any>, any>.

  2. added
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    and removed
    BugA bug in TypeScript
    on May 24, 2017
  3. simonvizzini commented on May 24, 2017

    @simonvizzini
    Author

    Thank you, Mohamed Hegazy (@mhegazy)! In my concrete case I actually did have a concrete Props interface defined, I just didn't think about this when trying to come up with a minimal example (I'm still learning the nuances of the type system). Nonetheless, I'm simply unable to properly type the ref callbacks, especially when defining Props interfaces that extend other, generic props (e.g. interface Props extends React.HTMLProps<HTMLInput>). Typescript gave funny errors along the lines of "got type A but expected B", where A extends from B, and when I changed the type def to type to B it said "got B but expected C", where B again extends from C...

    It's most likely a misunderstanding on my side, still learning ts and so I'll come up with a more concrete example today or tomorrow.

  4. mhegazy commented on May 24, 2017

    @mhegazy
    Contributor

    this works for me. if you have a sample we can look more.

    interface ContainerProps { 
        someProp: number;
    }
    class Container extends React.Component<ContainerProps, any> {
        render() {
            return <div ref={p => 0} />; // p is HTMLDivElement
        }
    }
    
    var x = <Container someProp={3} ref={p => 0} /> // p is Container
  5. simonvizzini commented on May 30, 2017

    @simonvizzini
    Author

    Thank you, your example indeed works. Finally I had time to come up with a better example that doesn't work for me. I'm probably doing something wrong I think:

    interface MyInputProps extends React.ChangeTargetHTMLProps<HTMLInputElement> {
        onValid?(): boolean;
        onInvalid?(): boolean;
    }
    
    class MyInput extends React.Component<MyInputProps, any> {
        render() {
            const { onValid, onInvalid, ...inputProps } = this.props;
            return (
                <input { ...inputProps } />
            );
        }
    }
    
    var x = <MyInput ref={p => 0} /> // p is any

    When I try to explicitly set a type like ref={(p: MyInput) => 0} then I get following compiler error:

    message: 'Type '{ ref: (p: MyInput) => number; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyInput> & Readonly<{ children?: ReactNode; }> & R...'.
      Type '{ ref: (p: MyInput) => number; }' is not assignable to type 'Readonly<MyInputProps>'.
        Types of property 'ref' are incompatible.
          Type '(p: MyInput) => number' is not assignable to type 'Ref<HTMLInputElement>'.
            Type '(p: MyInput) => number' is not assignable to type '(instance: HTMLInputElement) => any'.
              Types of parameters 'p' and 'instance' are incompatible.
                Type 'HTMLInputElement' is not assignable to type 'MyInput'.
                  Property 'render' is missing in type 'HTMLInputElement'.'
    source: 'ts'
    

    When I try to set the type to HTMLInputElement I get:

    message: 'Type '{ ref: (p: MyInput) => number; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyInput> & Readonly<{ children?: ReactNode; }> & R...'.
      Type '{ ref: (p: MyInput) => number; }' is not assignable to type 'Readonly<MyInputProps>'.
        Types of property 'ref' are incompatible.
          Type '(p: MyInput) => number' is not assignable to type 'Ref<HTMLInputElement>'.
            Type '(p: MyInput) => number' is not assignable to type '(instance: HTMLInputElement) => any'.
              Types of parameters 'p' and 'instance' are incompatible.
                Type 'HTMLInputElement' is not assignable to type 'MyInput'.'
    source: 'ts'
    

    Clearly I have no idea what's going on here and I'm just randomly throwing types at my code until the compiler stops complaining 😆

  6. simonvizzini commented on May 30, 2017

    @simonvizzini
    Author

    I'm so sorry for spamming your inbox :) I think I finally figured out why TS is complaining (somehow I always missed the Property 'render' is missing in type 'HTMLInputElement'.' part of the error message, which gave me a hint in the right direction):

    When I change the props interface to this:

    interface MyInputProps extends React.ChangeTargetHTMLProps<MyInput & HTMLInputElement> {}

    (note the MyInput & HTMLInputElement) then the compiler is happy. Automatic type inference of p is still any (not sure if this is a bug or a limitation), but at least I can explicitly define the type in the ref callback now. Could you confirm if I'm doing this right?

  7. mhegazy commented on May 31, 2017

    @mhegazy
    Contributor

    React.ChangeTargetHTMLProps extends React.ClassAttributes which overrides ref to be a union type. here is the definition from react declaration file on DT.

        type Ref<T> = string | ((instance: T) => any);
    
        interface ClassAttributes<T> extends Attributes {
            ref?: Ref<T>;
        }

    The result here is that there is no contextual type for the function, and this p is any. this looks like a bug that we should fix. this should behave the same way it does with something like:

    declare var x: string | ((a: HTMLInputElement) =>void);
    x = a => { };
  8. added
    BugA bug in TypeScript
    and removed
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    on May 31, 2017
  9. 17 remaining items

  10. timc13 commented on Apr 19, 2018

    @timc13

    would love to see this get fixed!

  11. modified the milestones: TypeScript 2.9, on Apr 26, 2018
  12. ImAbhishekTomar commented on Jun 6, 2021

    @ImAbhishekTomar

    i am also facing similar type of issue.

    ERROR

    
    (JSX attribute) ref: React.Dispatch<(prevState: undefined) => undefined>
    Type 'Dispatch<(prevState: undefined) => undefined>' is not assignable to type 'LegacyRef<HTMLButtonElement>'.
      Type 'Dispatch<(prevState: undefined) => undefined>' is not assignable to type '(instance: HTMLButtonElement) => void'.
        Types of parameters 'value' and 'instance' are incompatible.
          Type 'HTMLButtonElement' is not assignable to type '(prevState: undefined) => undefined'.
            Type 'HTMLButtonElement' provides no match for the signature '(prevState: undefined): undefined'.ts(2322)
    index.d.ts(140, 9): The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & Props<"button", ButtonRenderPropArg, ButtonPropsWeControl>'
    

    Screenshot 2021-06-04 at 3 05 48 AM

  13. Andarist commented on Nov 1, 2024

    @Andarist
    Contributor

    The reported case is about the parameter not being contextually typed for this ref. It works today so I think it's fair to close this issue

    cc Ryan Cavanaugh (@RyanCavanaugh) Jake Bailey (@jakebailey)

  14. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptDomain: JSX/TSXRelates to the JSX parser and emitter

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions