This is a great starting tutorial, and got me up to speed quickly for converting react to typescript.
If I follow exactly. It works. However, I wanted to use this as a base to learn from , and am running into issues if I am using a component that is a class instead of a straight functional component.
`
class Hello extends React.Component
render() {
const { name, enthusiasmLevel = 1 } = this.props;
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}
}
`
is the example for writing this out as a class.
If I modify it slightly , and modify the interface for the props my code looks like this.
`import * as React from 'react';
import './Hello.css';
export interface Props {
name: string;
enthusiasmLevel?: number;
onIncrement?: () => void;
onDecrement?: () => void;
}
class Hello extends React.Component
render() {
const { name, enthusiasmLevel = 1 } = this.props;
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
<div>
<button onClick={this.props.onDecrement}>-</button>
<button onClick={this.props.onIncrement}>+</button>
</div>
</div>
);
}
}
export default Hello;
function getExclamationMarks(numChars: number) {
return Array(numChars + 1).join('!');
}`
However this blows up my container. I get a failed to compile error:
/src/containers/Hello.tsx
(20,61): error TS2345: Argument of type 'typeof Hello' is not assignable to parameter of type 'Component<{ enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnthusiasm; ..
.'.
Type 'typeof Hello' is not assignable to type 'StatelessComponent<{ enthusiasmLevel: number; na
me: string; } & { onIncrement: () => IncrementEnt...'.
Type 'typeof Hello' provides no match for the signature '(props: { enthusiasmLevel: number; n
ame: string; } & { onIncrement: () => IncrementEnthusiasm; onDecrement: () => DecrementEnthusiasm
; } & { children?: ReactNode; }, context?: any): ReactElement<any>'.
What am I doing wrong with using a class instead of functional component.
_Updated: March 11th, 2018_
See @serenaz 's answer below for a solution that correctly annotates the Props type within the component. e.g.:
// Props being the Props interface imported from Hello.tsx
export default connect<Props>(mapStateToProps, mapDispatchToProps)(Hello);
Had this problem also (Error message and all). Don't understand the issue completely however was able to circumvent the problem by the following:
In containers/Hello.tsx replace the export default connect(...) with:
export function mergeProps(stateProps: Object, dispatchProps: Object, ownProps: Object) {
return Object.assign({}, ownProps, stateProps, dispatchProps);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
mergeProps)(Hello);
This implements the same mergeProps function described by the docs (see react-redux mergeProps) BUT makes the type checker happy.
Remember to make the required modifications for the end of the tutorial. My components/Hello.tsx looked as follows:
import * as React from 'react';
import './Hello.css';
export interface Props {
name: string;
enthusiasmLevel?: number;
onIncrement?: () => void;
onDecrement?: () => void;
}
class Hello extends React.Component<Props, object> {
getExclamationMarks(numChars: number) {
return Array(numChars + 1).join('!');
}
render() {
const { name, enthusiasmLevel = 1, onIncrement, onDecrement } = this.props;
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + this.getExclamationMarks(enthusiasmLevel)}
</div>
<div>
<button onClick={onDecrement}>-</button>
<button onClick={onIncrement}>+</button>
</div>
</div>
);
}
}
export default Hello;
My final tutorial can be found at: https://github.com/3ygun/tutorial-react-with-ts < outdated see above
I had the exact same issue in the same circumstances and the above solution by @3ygun worked for me 馃憤
same issue as link with #29 #34 #36 #37
I have exactly the same error...
@3ygun Dude, you rock!!! Spent 2 days on this... Went through all types of local trials and errors (I started off with the extends React.Component approach, and after a day I realized it worked as a function()). More than a few dozen searches and more of the same TS2345 error, tried changing TypeScript versions. ...I stripped everything bare! And what's more strange is it works as expected on CodePen (without your fix - which is why I figured it was a version issue; which it wasn't). Anyways... Thanks!!!
@KDCinfo No problem felt the same way when I hit the issue. Tried to fix it with #39 but no @Microsoft rep has commented 馃槩
Exactly same issue, when adding mergeProps like @3ygun advised it works! The hell is going on there... :,(
i have same problem, but has not being resolve yet.
the type like:
export interface StoreState {
user: User;
list: CardList;
}
export interface User {
id: number;
name: string;
phone: number;
}
export interface CardList {
cards: Array<Array<CardItem>>;
pageNum: number;
pageSize: number;
history: Set<number>;
}
export interface CardItem {
id: string;
title: string;
describe: string;
img: string;
status?: boolean;
}
the reducer like:
export const userReducer = (user: User = {} as User, action: OperateUser): User => {
switch (action.type) {
case OPERATE_USER:
return user = action.user ;
default:
return user;
}
};
export const listReducer = (list: CardList = {} as CardList, action: OperateList): CardList => {
switch (action.type) {
case OPERATE_LIST:
list.cards[action.pageNum - 1] = action.items;
list.history.add(action.pageNum);
return list;
default:
return list;
}
};
export default combineReducers<StoreState>({userReducer, listReducer});
i try to use your plan but isn't work...
the log is :
./src/index.tsx
(19,13): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Pick<Props, "user" | "list" | "operateUs...'.
Type '{}' is not assignable to type 'Readonly<Pick<Props, "user" | "list" | "operateUser" | "operateList"> & object>'.
Property 'user' is missing in type '{}'.
the repos is :
https://coding.net/u/zhougonglai/p/react-ts-webpack/git/tree/create_react_app
I feel this should be better documented.
This is what I ended up working with, it's a bit out of context but types should make sense:
// Redux Default State
type State = {
auth: {
token: string
// ...
}
};
interface StateProps {
auth: State['auth'];
}
interface DispatchProps {
newToken: Function;
}
const mapStateToProps = (state: State): StateProps => ({
auth: state.auth,
});
const mapDispatchToProps = (dispatch: any): DispatchProps => ({
newToken: bindActionCreators(newToken, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(PresentationalComponent as any);
The idea is to make it work without using as any
@zhougonglai Please how did you get your issue fixed. I have been battling with this problem for a while now
@mrjobosco I learned how to give up... the thing I'm going to use for a long time to try. My job is a Vue developer. Although I love the react!
I think the correct solution is to use the generics.
The Hello class is generic class with Props type.
class Hello extends React.Component<Props, object> {...}
And for connect function we should also to set the same type:
export default connect<Props>(mapStateToProps, mapDispatchToProps)(Hello);
@3ygun or somebody, please correct me if I'm wrong.
@sgrigorev your answer seems the correct one. Might be a type inference problem and you are only helping the compiler. Thanks for your help.
I landed here on a google search. I wasn't using the starter or sample provided here, but this was the first result. I wanted to mention that @sgrigorev got me in the right direction. I'm still converting my project to Typescript, but my props contained a nullable type which I guess was incompatible with what's in redux. I changed it to a non-nullable type and TS is happy. I appreciate this thread.
I know this is a very old issue, but after I found my own solution, I'd better share it for another.
The problem is here:
onIncrement?: () => void;
onDecrement?: () => void;
The dispatch function returned an action instead of nothing, that why TypeScript complained, and it's a good thing.
You could either fix it the easy way:
onIncrement?: () => any;
onDecrement?: () => any;
or the hard way:
onIncrement?: () => IncrementEnthusiasm;
onDecrement?: () => DecrementEnthusiasm;
I'd refer the former since the container shouldn't need to know what was dispatched.
Thanks, @3ygun, you saved my day!
Hello, I am trying to use the @3ygun solution, but when I am trying to use my component in a Route is complaining about missing props
// Edit.tsx
interface ReduxState {
views: View[]
}
interface ReduxAction {
updateView: (view: View) => void;
}
interface Props extends ReduxState, ReduxAction {
name: string;
}
class Edit extends React.component<Props, {}> ...
export connect<Props>(mapStateToProps, mapDispatchToProps)<Edit>
and now I am trying to use this component in the route component
// App.tsx
<Route
path="/edit/:id"
render={({ match }: RouteComponentProps<{ id: string }>) => (
<Edit name={match.params.id} />
// ^^^^^^^^^^^^^^^^^^^^^^^^^
)}
/>
So... It complains saying that the updateView & views are missing, but this props are coming from redux... I don't know how to solve it. Any ideas? Thanks!
the easiest solution is the following:
function mapStateToProps(state, ownProps) {
return {
...ownProps,
// add your state props here
}
}
connect(mapStateToProps)(Component);
I used
// Props being the Props interface imported from Hello.tsx
export default connect<Props>(mapStateToProps, mapDispatchToProps)(Hello);
But got error:
Expected 0-4 type arguments, but got 1.
So I change the hello container to
`
interface StateProps {
name: string;
enthusiasmLevel?: number;
}
interface DispatchProps {
onIncrement?: () => void;
onDecrement?: () => void;
}
export function mapStateToProps ({enthusiasmLevel, languageName}: StoreState): StateProps {
return {
enthusiasmLevel,
name: languageName
}
}
export function mapDispatchToProps(dispatch: Dispatch
return {
onIncrement: () => dispatch(actions.incrementEnthusiasm()),
onDecrement: () => dispatch(actions.decrementEnthusiasm())
}
}
export default connect
to solve the problem
Argument of type 'Element' is not assignable to parameter of type '(prevState: null) => null'.
Type 'Element' provides no match for the signature '(prevState: null): null'.ts(2345)
React Hooks seems not supported well.
Most helpful comment
Updated Answer
_Updated: March 11th, 2018_
See @serenaz 's answer below for a solution that correctly annotates the Props
typewithin the component. e.g.:My outdated answer
Had this problem also (Error message and all). Don't understand the issue completely however was able to circumvent the problem by the following:
In
containers/Hello.tsxreplace theexport default connect(...)with:This implements the same
mergePropsfunction described by the docs (see react-redux mergeProps) BUT makes the type checker happy.Remember to make the required modifications for the end of the tutorial. My
components/Hello.tsxlooked as follows:My final tutorial can be found at: https://github.com/3ygun/tutorial-react-with-ts < outdated see above