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
@NicholasBoll What's the type of Slice? You have:
render: (state: Slice) => JSX.Element
Shouldn't it be:
render: (state: State) => JSX.Element
?
@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
@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.
@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>
}
})
The Select example is one I've used related to #6395
@weswigham do you know what is going on here?
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.
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.