For example (in App.tsx)
export default connect(mapStateToProps)(App);
and then:
import App from './App';
...
<Provider store={store}>
{() =>
<App />
}
</Provider>
causes compile error: (TypeScript 1.6)
Error TS2604: JSX element type 'App' does not have any construct or call signatures.
If I change the type definition so that it returns a Function:
export function connect(mapStateToProps?: MapStateToProps,
mapDispatchToProps?: MapDispatchToPropsFunction|MapDispatchToPropsObject,
mergeProps?: MergeProps,
options?: Options): Function;
it compiles and works.
I think that the problem is with the definition of ClassDecorator. Right now it looks like that:
<TFunction extends Function>(target: TFunction): TFunction|void;
and that one does not work because of that void. That one is correct:
<TFunction extends Function>(target: TFunction): TFunction;
It fixes the problem with injecting into JSX.
What do you think @tkqubo ?
@niba Actually the definition of ClassDedorator
is loose (I simply copy-and-pasted it from lib.d.ts
) and I totally agree with your proposal.
And if this decorator is to be used only on react Component
, the definition could be more specific. Or should it be?
Anyway this time I will fix to the simple definition you gave
<TFunction extends Function>(target: TFunction): TFunction;
Thanks @niba @schlaup !
I was thinking about
<T extends Component<any,any>>
but in this case typescript compiler will expect an instance of class and in React case we dont wan't to create a w new instance of App/Component
Hi @niba
I just came up with this code snippet and it can somehow compile:
import * as React from 'react';
import { Component } from 'react';
class ComponentInterface extends Component<any, any> { }
declare function componentWrapper<T extends (typeof ComponentInterface)>(component: T): T;
// below are the sample code
interface Prop {
property1: number;
someOtherProperty?: string;
}
interface State {
isLoaded: boolean;
state1: number;
}
class TestComponent extends Component<Prop, State> { }
let WrappedTestComponent = componentWrapper(TestComponent);
// can compile!
let component = <TestComponent property1={1} />;
component = <WrappedTestComponent property1={1} />;
function foo() { }
class NotComponent { }
// compile error!
//componentWrapper(NotComponent);
// compile error!
//componentWrapper(foo);
Maybe I can apply this componentWrapper
for connect
decorator :smile:
Hi @tkqubo
It can compile because of this empty body of ComponentInterface, when you add something to the interface it will stop compile.
Generally I like it more than the solution with TFunction. With this we have some information what we should pass to the connect method.
Good job!
ComponentInterface
is meant to be a kind of workaround since TypeScript doesn't allow generics like below:
// this is what I actually want to do, but doesn't compile
<T extends (typeof Component<any,any>)>
So it is not supposed to add something to it. You just need to make your class extend Component<P, S>
to make this componentWrapper
work
Thanks for your comment @niba
connect does not work with StatelessComponent from 0.14. It should accept Component
I think TypeScript 1.7 itself doesn't seem to support stateless component yet. https://github.com/Microsoft/TypeScript/issues/5478
So connect
should be modified after 1.8 has been released.
Seems the issue is for JSX/TSX. I'm not using that, just regular functions.
Just ran into this issue. Is a fix good to get applied now that TypeScript is at 1.8.2?
@jschaf sure, i also think so. i was preparing for stateless component. I'll make a PR soon
@tkqubo keep us posted on that PR!
I made a pr for react-redux
update for stateless component #8323
it merged. solved this issue?
@vvakame thanks for reminding. I think this issue can be closed if no further objections
For some reason I still get this error with the latest typing.
Here is the code:
interface CounterProps {
count: number;
onClick: any;
}
const Counter: React.StatelessComponent<CounterProps> = ({count, onClick}) => (
<div>
<h1>Counter below</h1>
<p onClick={onClick}>{count}</p>
</div>
);
Counter.propTypes = {
count: React.PropTypes.number.isRequired,
onClick: React.PropTypes.func.isRequired
};
const mapStateToProps = (state: ICounter) => {
return { count: state.counter };
};
const mapDispatchToProps = (dispatch) => {
return { onClick: dispatch(increaseCounter) };
};
const CounterComponent = connect(mapStateToProps, mapDispatchToProps)(Counter);
The error I am getting:
src/app.tsx(50,71): error TS2345: Argument of type 'StatelessComponent
' is not assignable to parameter of type 'ComponentClass<{ count: number; } & { onClick: any; }>'.
Type 'StatelessComponent' provides no match for the signature 'new (props?: { count: number; } & { onClick: any; }, context?: any): Component<{ count: number; } & { onClick: any; }, {}>'
changed definition to
function createStore(reducer: Reducer, initialState?: any, enhancer?: Function): Store;
for now
I get the same as @alexeyzimarev. Ping @tkqubo
Sorry I missed the comments! pong :rocket:
I think changing the ComponentDecorator
definition might help.
// before
interface ComponentDecorator<TOriginalProps extends Props<any>, TOwnProps extends Props<any>> {
(component: ComponentClass<TOriginalProps>): ComponentClass<TOwnProps>;
}
// after
interface ComponentDecorator<TOriginalProps extends Props<any>, TOwnProps extends Props<any>> {
(component: ComponentClass<TOriginalProps>|StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}
@use-strict sorry for bothering you, but do you think this modification is appropriate? if so I will create a PR for that.
But for now, casting Counter
to any
will make your lines of code pass compilation. @alexeyzimarev
const CounterComponent = connect(mapStateToProps, mapDispatchToProps)(Counter as any);
@tkqubo , can't be 100% sure, but it looks OK. In the end, the tests should pass. You should also add this new example to the test file.
If I have a component that accepts some props, let's say
type MyViewPropType = {
progress: number
}
class MyView extends React.Component<MyViewPropType, any> {
}
And I want the connect
ed version to retain typing information, so I did
connect<MyView>(mapStateToProps)(MyView)
But the I got Type 'MyView' does not satisfy the constraint 'Function'. Property 'apply' is missing in type 'MyView'.
What is the proper way to do this?
I think the generic type parameter you've given is not correct. Depending on how you want to pass progress
prop to the Component, connect
type signature will differ.
progress
value to the component, not relying on react-redux
connect
type MyViewPropType = {
progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): {} {
return {};
}
const ConnectedMyView = connect<{}, {}, MyViewPropType>(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView progress={42} />;
connect
In this case, you have to provide type parameter on mapStateToProps
instead, otherwise connect
doesn't know how to construct MyView
's MyViewPropType
from state props, dispatch props, and own props
type MyViewPropType = {
progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any, ownProp?: MyViewPropType): {} {
return {};
}
const ConnectedMyView = connect(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView progress={42} />;
progress
value via mapStateToProps
to the componenttype MyViewPropType = {
progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): MyViewPropType {
return { progress: 42 };
}
// here connect's type parameters aren't necessarily required
const ConnectedMyView = connect<MyViewPropType, {}, {}>(mapStateToProps)(MyView);
let myViewElement = <ConnectedMyView />;
progress
value via mapDispatchToProps
to the componenttype MyViewPropType = {
progress: number
}
class MyView extends React.Component<MyViewPropType, any> { }
function mapStateToProps(state: any): {} {
return {};
}
function mapDispatchToProps(dispatch: Redux.Dispatch): MyViewPropType {
return { progress: 42 };
}
// here connect's type parameters aren't necessarily required
const ConnectedMyView = connect<{}, MyViewPropType, {}>(mapStateToProps, mapDispatchToProps)(MyView);
let myViewElement = <ConnectedMyView />;
Four examples above do compile in my environment. I hope this will help you.
@tkqubo Thank you very much! That's very detailed and helpful.
@tkqubo Thanks for posting this as I came across this similar issue. I'm new to Typescript but I'm wondering if there's a way for connect to produce a combination of the inferred types of mapStateToProps
and mapDispatchToProps
rather than an intersection type. It seems like that would solve the issue but I'm not sure if it's possible.
@ochowie Sorry to be late. I need to know what you want to do more in detail. Could you show us example lines regardless of they compile or not?
I'm still new to TS so this might not be possible but what I wasn't sure of is why the following code
mapStateToProps = (state) => ({
test: state.testString
})
mapDispatchToProps = (dispatch) =. ({
incr: (field) => {
dispatch({type: 'TEST', field});
})
connect(mapStateToProps, mapDispatchToProps)(Component)
produces an intersection type of the two return objects rather than a combination. Is it because the TS compiler thinks this is a more "general" way of describing the types? I would expect it to return a type of
{ test: String, incr: (string)=>{})
but instead it produces {test: String} & {incr: (string) =>{}}
Edit: I know based on your earlier example that I can make this work by adding generic types to connect
or specifying a return type on the two map functions. However, I was hoping to better understand why it was necessary. Also, if I went with the second approach (specifying the return type on the two map functions) then the members of the return type would have to be optional otherwise it would fail to build, correct?
Removing the
export const LoginForm = connect(mapStateToProps, mapDispatchToProps)(reduxForm({form: 'loginForm', validate})(Login));
is this related?
Any good examples of a work around of this? There are tons of issues listed here, but no solid workarounds/solutions
I was so excited about TypeScript until I hit this error...
Still waiting for fix on this, it hasn't been addressed yet. Re Writing these components in JSX for now, pulling them away from TS until react-redux and TS learn to live together better!
@evanjmg @savovs @stevematdavies
I will show you how I work with the connect, maybe that will help you.
First, in every project, I do module augmentation like that
import { Component, ComponentClass } from "react-redux";
declare module "react-redux" {
export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
<P extends TInjectedProps>(component: Component<P>): ComponentClass<Omit<P, keyof TInjectedProps> & TNeedsProps> & {
WrappedComponent: Component<P>;
};
allProps: TInjectedProps & TNeedsProps;
}
}
this code adds allProps
property, it will only be used by the typescript compiler to satisfy typings so it's not important that it doesn't exist in a js code
Now the example of container
import * as React from "react";
import { connect } from "react-redux";
import { ApplicationState } from "modules/app.reducers";
interface ITestContainerProps {
externalProp: number;
}
class TestContainer extends React.Component<ITestContainerProps & IConnectProps, any> {
render() {
this.props.testAction(); // typings ok
return (
<div>
{this.props.count /* typings works */}
</div>
);
}
}
const connectCreator = connect(
(state: ApplicationState) => ({
count: state.count,
}),
{
testAction: () => {},
},
);
type IConnectProps = typeof connectCreator.allProps;
export default connectCreator(TestContainer);
<TestContainer externalProp={5} /> // typings ok
definition of connect props is automatically generated by using that magic allProps
property that we added earlier. If you want to add any custom types that you don't pass from mapStateToProps
and mapDspatchToProps
you extend Props interface above the container
That's interesting, very much appreciated.
However, considering the amount of elements in our service, and the wide range of different props, state and redux-state, this is not a viable solution. In addition this seems a little overly complex, particularly if using composed components. This also doesn't seem to quite address the issue. Ill try and look into your concept more out of Interest.
Many Thanks
This seems to be an issue with the React typings, not just react-redux. I see similar issues on other components after updating my types.
Most helpful comment
I think the generic type parameter you've given is not correct. Depending on how you want to pass
progress
prop to the Component,connect
type signature will differ.Case 1. You want to directly pass
progress
value to the component, not relying onreact-redux
1.1: provide type parameter explicitly on
connect
1.2: don't provide type parameter on
connect
In this case, you have to provide type parameter on
mapStateToProps
instead, otherwiseconnect
doesn't know how to constructMyView
'sMyViewPropType
from state props, dispatch props, and own propsCase 2. You want react-redux to provide
progress
value viamapStateToProps
to the componentCase 3. You want react-redux to provide
progress
value viamapDispatchToProps
to the componentFour examples above do compile in my environment. I hope this will help you.