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

Type inference failing on JSX generics - works fine in a function call #23412

Closed
NicholasBoll opened this issue Apr 14, 2018 · 8 comments
Closed
Labels
Duplicate An existing issue was already created

Comments

@NicholasBoll
Copy link

NicholasBoll commented Apr 14, 2018

TypeScript Version: 2.7.2, 2.7.2-dev.20180207, 2.7.2-insiders.20180209, 2.8.0, 2.8.1

Search Terms: JSX, Inference, React

Code

import * as React from 'react'

declare function Connected<State extends {}, Slice>(props: {
  mapState: (state: State) => Slice,
  render: (slice: Slice) => JSX.Element
}): JSX.Element

interface State {
  foo: string
}

// JSX
<Connected
  mapState={(state: State) => state.foo}
  render={(slice) => {
    slice // Slice
    slice.length // any
    slice.foo // [ts] Property 'foo' does not exist on type 'string'.
    return <div>{slice}</div>
  }}
/>

// Function call
Connected({
  mapState: (state: State) => state.foo,
  render: (slice) => {
    slice // string
    slice.length // number
    slice.foo // [ts] Property 'foo' does not exist on type 'string'.
    return <div>{slice}</div>
  }
})

tsconfig.json

{
  "compilerOptions": {
    "strict": true,
    "module": "commonjs",
    "target": "es5",
    "jsx": "react",
    "lib": ["dom", "es2017.object", "es2016"],
  },
  "include": ["**/*"]
}

Expected behavior:
slice type in the render function is string

Actual behavior:
slice type in the render function is Slice

Playground Link: Playground doesn't support JSX

Related Issues:

Additional Notes:
I narrowed it down to breaking between 2.7.1 and 2.7.2. I tried multiple versions under 2.7.1 down to 2.5.3 and they all worked as expected.

Type checking works. Both throw an error accessing slice.foo, but when mousing over slice in the JSX example says 'Slice' and a valid property access gives a type of any

I didn't really know how to classify this bug. Feel free to change the title if it doesn't make sense

@lucasbasquerotto
Copy link

@NicholasBoll What's the type of Slice? You have:

render: (state: Slice) => JSX.Element

Shouldn't it be:

render: (state: State) => JSX.Element

?

@NicholasBoll
Copy link
Author

@lucasbasquerotto Slice is supposed to be inferred from the mapState function. I can call the argument in the render function slice if that is less confusing

@lucasbasquerotto
Copy link

lucasbasquerotto commented Apr 17, 2018

@NicholasBoll I understand. So, about your problem, you have:

mapState={(state: State) => state.foo}

The type Slice will be infered as string because state.foo is a string and mapState returns a Slice object.

You could try:

<Connected
  mapState={(state: State) => ({ foo: state.foo })}
  render={(state) => {
    console.log('state', state);
    // state doesn't have the length property, but state.foo has
    console.log('state.foo', state.foo);
    return <div>{state}</div>
  }}
/>

Although it doesn't give an error with state.foo, it seems the type of state.foo is any, I don't know if this is by design, but should be a string, just like when using with a function.

If I write a wrong property the error is shown correctly, but if I place the cursor above a correct property it shows any and I don't have autocompletion.

@NicholasBoll
Copy link
Author

@lucasbasquerotto Exactly. I updated the example above to use slice instead of state to not mix the 2 up. The idea of this component is a render-prop style of react-redux that takes a mapState that extracts properties off a store's State.

Here's another example that is similar that shows the problem as well:

declare function Select<T>(props: {
  options: T[],
  render: (item: T) => JSX.Element
}): JSX.Element

<Select
  options={[{
    foo: 'bar',
  }, {
    foo: 'baz'
  }]}
  render={item => {
    item // T instead of { foo: string }
    item.foo // any
    return <div>{item.foo}</div>
  }}
/>

Select({
  options:[{
    foo: 'bar',
  }, {
    foo: 'baz'
  }],
  render: item => {
    item // { foo: string }
    item.foo // string
    return <div>{item.foo}</div>
  }
})

@NicholasBoll
Copy link
Author

The Select example is one I've used related to #6395

@mhegazy
Copy link
Contributor

mhegazy commented Apr 18, 2018

@weswigham do you know what is going on here?

@weswigham
Copy link
Member

The key comment in the OP:

Type checking works. Both throw an error accessing slice.foo, but when mousing over slice in the JSX example says 'Slice' and a valid property access gives a type of any

This is a duplicate of #22636.

@mhegazy mhegazy added the Duplicate An existing issue was already created label Apr 18, 2018
@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants