Typescript-react-starter: Hello.tsx when hello is a class, it breaks....

Created on 25 May 2017  路  22Comments  路  Source: microsoft/TypeScript-React-Starter

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.

Most helpful comment

Updated Answer

_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);

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.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

All 22 comments

Updated Answer

_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);

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.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): DispatchProps {
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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

user135711 picture user135711  路  6Comments

BenjaminWatts picture BenjaminWatts  路  6Comments

Yang09701194 picture Yang09701194  路  7Comments

Arelav picture Arelav  路  4Comments

Lxxyx picture Lxxyx  路  9Comments