marchello
marchello

Reputation: 2136

Event handling inside stateless functional components in React, Typescript

I have an onClick event in one of my functional components:

import * as React from 'react';

const BreadCrumbs: React.SFC<{level: Array<string>, changeLevel: Function}> = ({level, changeLevel}) => {
    return (
      <div>
        {
          level.map(function(elem, i) { 
            return <span key={i} onClick={changeLevel}>{elem}</span> 
          })
        }
      </div>
    )
}

export default BreadCrumbs;

In the parent component I supply the <BreadCrumbs> component with props of an array and a function reference:

<BreadCrumbs level={this.state.levels} changeLevel={this.changeLevel}/>

This doesn't work because in the <BreadCrumbs> component, the Typescript compiler fails with the error (on return <span> line):

[ts]
Type '{ key: number; onClick: Function; children: string; }' is not assignable to type 'DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>'.
  Type '{ key: number; onClick: Function; children: string; }' is not assignable to type 'HTMLAttributes<HTMLSpanElement>'.
    Types of property 'onClick' are incompatible.
      Type 'Function' is not assignable to type '((event: MouseEvent<HTMLSpanElement>) => void) | undefined'.
        Type 'Function' is not assignable to type '(event: MouseEvent<HTMLSpanElement>) => void'.
          Type 'Function' provides no match for the signature '(event: MouseEvent<HTMLSpanElement>): void'.
(JSX attribute) onClick: Function

However, if I modify my code to this:

const BreadCrumbs: React.SFC<{level: Array<string>, changeLevel: Function}> = ({level, changeLevel}) => {
    function something(i: number) {
      changeLevel(i);
    }
    return (
      <div>
        {
          level.map(function(elem, i) { 
            return <span key={i} onClick={something.bind(this, i)}>{elem}</span> 
          })
        }
      </div>
    )
}

export default BreadCrumbs;

Everything works fine. Why doesn't the first version work?

Upvotes: 0

Views: 1611

Answers (1)

Jack Q
Jack Q

Reputation: 311

In TypeScript, it use a strongly typed manner to perform type checking on functions, which takes the parameters and returned value of a function into consideration. Thus, for the parameter (React component prop) changeLevel, its type should be declared as (event: React.MouseEvent<HTMLSpanElement>) => void or compatible types (like (e: React.MouseEvent<Element>) => void, () => void, or () => any). The Function type is not used for function type declaration, but for typing the global constructor of any function object, which lacks the information about function object and should be avoided when typing a function.

You can change your code to following version to pass TypeScript's checking.

const BreadCrumbs: React.SFC<{level: Array<string>, changeLevel: () => void}> = ({level, changeLevel}) => {
    return (
      <div>
        {
          level.map(function(elem, i) { 
            return <span key={i} onClick={changeLevel}>{elem}</span> 
          })
        }
      </div>
    )
}

Actually, in the second version, the return type of bind function is any, which can be assigned to variable of any type in TypeScript. TypeScript performs no type checking in that version.

Upvotes: 1

Related Questions