Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Typescript] type for ref in <Input/> not sufficiently strict #15072

Closed
2 tasks done
tianhuil opened this issue Mar 27, 2019 · 10 comments
Closed
2 tasks done

[Typescript] type for ref in <Input/> not sufficiently strict #15072

tianhuil opened this issue Mar 27, 2019 · 10 comments
Labels
external dependency Blocked by external dependency, we can’t do anything about it typescript

Comments

@tianhuil
Copy link

The type for ref in Input is too general. It only requires (among other things) React.RefObject<any>. But the equivalent requirement for <input> is React.RefObject`.

  • This is not a v0.x issue.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior 🤔

In the following app, the poorly typed myRef does not generate an error when passed to <Input/> but does generate a type error when passed to <input/>

import React from 'react';
import Input from '@material-ui/core/Input'
import './App.css';

const App = () => {
  const myRef = React.useRef()

  return (
    <div className="App">
      <input name="foo" ref={myRef}/> // type error
      <Input inputRef={myRef}></Input> // no type error
    </div>
  );
}

export default App;

Current Behavior 😯

It should generate an error on both <Input/> and <input/>. This would be helpful to so typechecking can help automatically check for expressions like myRef.current.value in subsequent usage.

Steps to Reproduce 🕹

Link: clone this repo and run npm i. You'll observe the error in App.tsx.

Context 🔦

This would be helpful to so typechecking can help automatically check for expressions like myRef.current.value in subsequent usage.

Perhaps related to #10750

Your Environment 🌎

Tech Version
Material-UI v3.9.2
React 16.8.5
TypeScript 3.3.4000
@eps1lon eps1lon added this to the v4 milestone Mar 27, 2019
@eps1lon
Copy link
Member

eps1lon commented Mar 27, 2019

It is currently typed as any. Since the props aren't generic and we need type information about inputComponent to know what type inputRef would be. I'm not sure using unknown would be the better choice here. You could type it as unknown yourself to have full type safety.

That whole logic can be simplified once v4 lands. The longterm goal is to drop inputRef and encourage <Input inputProps={{ ref }} />. Once all our props are generic this should give you proper type safety.

@eps1lon eps1lon removed this from the v4 milestone Mar 27, 2019
@tianhuil
Copy link
Author

tianhuil commented Mar 27, 2019

Thanks for the quick response @eps1lon. Could you provide a code snippet for

You could type it as unknown yourself to have full type safety.

I'm not sure if you mean fork a copy of @material-ui and change the type signature or if you mean something else?

@eps1lon
Copy link
Member

eps1lon commented Mar 27, 2019

I'm not sure if you mean fork a copy of @material-ui and change the type signature or if you mean something else?

You only have to fork the declaration for Input.d.ts and then use a paths alias in your tsconfig e.g

"paths": {
  "@material-ui/core/Input": ["path/to/forked/Input"]
}

You have to be aware though that the instance in inputRef depends on the type of the inputComponent. If you can create a declaration that covers those cases I would appreciate a PR.

@tianhuil
Copy link
Author

tianhuil commented Mar 27, 2019

Thanks @eps1lon!

the instance in inputRef depends on the type of the inputComponent

From what I can tell, you want to handle two types?

  • If multiline is true then it should be of type React.RefObject<HTMLTextAreaElement>
  • If multiline is false then it should be of type React.RefObject<HTMLInputElement>

I don't think I can be precise about the multiline condition but React.RefObject<HTMLTextAreaElement | HTMLInputElement> would be a more narrow (but sufficiently flexibe) typing than we have now?

@eps1lon
Copy link
Member

eps1lon commented Mar 27, 2019

I don't think I can be precise about the multiline condition but React.RefObject<HTMLTextAreaElement | HTMLInputElement> would be a more narrow (but sufficiently flexibe) typing than we have now?

That helps but it's not necessarily true if another component is used via inputComponent.

@tianhuil
Copy link
Author

Thanks! I don't have a lot of context about the intended use case for <Input/> but from the docs it appears that

You can use third-party libraries to format an input. You have to provide a custom implementation of the element with the inputComponent property. The provided input component should handle the inputRef property. The property should be called with a value implementing the HTMLInputElement interface.

Do you have a list of HTML*Element that you would like to support or is this a thing where you're afraid to break backward compatibility if lots of other people have used custom components that don't satisfy the full type interface? Happy to implement the former but don't really think there's a solution for the latter.

@eps1lon
Copy link
Member

eps1lon commented Mar 31, 2019

@eps1lon
Copy link
Member

eps1lon commented Apr 4, 2019

@tianhuil There is a bigger picture issue when typing refs too strict. What happens is that everybody who only cares about inputRef being an HTMLElement (e.g. just for focusing) cannot use this less specific ref. <InputBase ref={React.createRef<HTMLElement>()} /> would be rejected by the type checker.

In the current state of TypeScript we can't offer a sound ref type. Soundness is the responsibility of the type user e.g. in this case you would have to declare your ref as React.createRef<HTMLInputElemen>(). It would make the read safe but you need to verify manually that the write by react is correct. The safest option would be to use React.createRef<unknown> which is probably what we're implementing. Then you can narrow the type manually like you prefer (casting, instanceOf checks, or other type guards).

You can check out https://github.com/eps1lon/typescript-react-ref-issue/blob/626f5197601c35a13e1207a8f4ab7e029454ecb6/index.tsx for a better overview what's currently accepted and rejected by typescript.

@eps1lon eps1lon added this to the v4 milestone Apr 4, 2019
@eps1lon
Copy link
Member

eps1lon commented Apr 17, 2019

For components with generic props we infer the exact ref from the type of the component prop. Everything else will use unknown and we let the user control soundness of their ref.

For a more detailed explanation see #15199. Removing this from the v4 milestone until we get better types for refs i.e. write-only or configurable variance or a better solution is proposed.

@eps1lon eps1lon removed this from the v4 milestone Apr 17, 2019
@eps1lon eps1lon added the external dependency Blocked by external dependency, we can’t do anything about it label Jun 13, 2019
@eps1lon
Copy link
Member

eps1lon commented Jun 13, 2019

Closing as not actionable for us. For more context see #15199.

@eps1lon eps1lon closed this as completed Jun 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
external dependency Blocked by external dependency, we can’t do anything about it typescript
Projects
None yet
Development

No branches or pull requests

2 participants