TypeScript Error TS7031 Makes me go "huh?"

Note: I originally posted this on Medium.

The Problem

The following is a snippet of React + TypeScript that I just wrote (simplified so we can focus on the error it throws):

const Foo = ({className}) => 
  <Popper className={className + " someCustomClassName"} />

This throws the following TypeScript error:

TS7031: Binding element 'className' implicitly has 'any' type.

I understand that this means that I didn’t explicitly declare the type for the className variable, so it “implicitly” has the any type, but I don’t understand why this is a problem. It seems counter-intuitive - shouldn’t any types allow… any types??

Down the Rabbit Hole

A little bit of internet searching later, and I found the list of error codes for TypeScript in the repo: https://github.com/Microsoft/TypeScript/blob/2.1/src/compiler/diagnosticMessages.json#L3072

That shows the error message source:

"Binding element '{0}' implicitly has an '{1}' type.": {
  "category": "Error",        
  "code": 7031    
},

…not very helpful, but it’s always nice to find things in source, rather than dumb blog posts (like this one)… So, what is the problem, TypeScript compiler? I know, I know, I can just declare the type for my className variable and move on… but I want to understand why.

A Fix, but I Still Know Nothing

So, for example, this will “fix” the problem:

const Foo = ({className}:{[key:string]:any}) => 
  <div className={className + " someCustomClassName"} />

In that snippet, I “explicitly” declared all keys on the first argument object as string and all values on that object as any. So, the only difference that I can tell is that I am explicitly declaring any, rather than implicitly. I still don’t know why this is better, because TypeScript is capable of inferring types, meaning I don’t have to write const foo: number = 5, I can just write const foo = 5 and TypeScript “infers” (“derive as a conclusion from facts or premises”) that the type of variable foo is number. In this simple foo example, TypeScript is using the most straightforward version of type inference:

This kind of inference takes place when initializing variables and members, setting parameter default values, and determining function return types.

The type of variable foo is inferred to be number when the variable is initialized. There are two other ways that types are inferred: best common type, and contextual type, but I don’t think they are relevant to the scope of this post.

Down The Rabbit Hole, Chapter 2

It may be useful to briefly think about the basic structure and syntax of my original code snippet. Let’s look at it again:

const Foo = ({className}) => 
  <Popper className={className + " someCustomClassName"} />

I am using object destructuring (AKA destructuring assignment) to extract the className from the first argument object. The more verbose way to write this is:

const Foo = (obj) => {
  const className = obj.className
  return <Popper className={className + " someCustomClassName"} />
}

Destructuring is a feature of JavaScript (ECMAScript 2015), so it is also available in TypeScript.

In Another Dimension…

One way to use type inference here is to set a default value for the className parameter:

const Foo = ({className = 'someDefaultValue'}) => 
  <Popper className={className + " someCustomClassName"} />

This is making use of type inference via improved checking for destructuring object literal (also here): “Properties with default values in the object binding pattern become optional in the object literal.”. So, we explicitly set a default value, which is a string, and the className parameter is implicitly inferred as string. Unfortunately, for my case, I don’t want to define a default value, so this won’t work for me.

TLDR; No Implicit Any

The answer to this riddle is the TypeScript --noImplicitAny compiler option:

Raise error on expressions and declarations with an implied any type.

This option is disabled by default, but I have a tsconfig.json file (TypeScript config) that I forgot about, which enables the option;

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "jsx": "react"
  },
  "include": ["./src/**/*"]
}

According to BASARAT, this is helpful if you want “high safety” when declaring function arguments in TypeScript.