React TypeScript - Why does Boolean() behave differently than double NOT (!!) operator when trying to conditionally render with && operator?

Viewed 645

When working with javascript I always assumed that the double NOT operator (!!) behaves the same way as Boolean(), when trying to get a true / false out of a statement; however, since working with TypeScript I recently came across a scenario where they behaved differently, and I am trying to understand why that is. Take the following code for example:

import React from 'react'

interface Props {
    myNumber: number | null
}

const Test: React.FC<Props> = ({ myNumber }) => {
    return (
        <>
            {/* Example 1 */}
            {!!myNumber && <span>{myNumber.toString()}</span>}

            {/* Example 2 */}
            {Boolean(myNumber) && <span>{myNumber.toString()}</span>}
        </>
    )
}

export default Test

Under example #1 the code works as I expect it: If the is rendered that means that myNumber passed the !! check, so it can never be null, thus myNumber.toString() will not return an error.

Under example #2 however, myNumber.toString() will return an error stating that "Object is possibly null", even though this code should only be reached if the Boolean(myNumber) returns true, and Boolean(null) should always be false.

Am I wrong thinking they should behave exactly the same? Is there something related to the TypeScrtipt compiler that I am missing? Thanks!

1 Answers

Boolean is typed as a function of type BooleanConstructor.

BooleanConstructor can be further broken down as <T>(value?: T | undefined) => boolean.

TypeScript does not consider the inner workings of a function when checking to see if a value may be null | undefined.

// Here TypeScript can directly infer that myNumber is indeed truthy 
// by the preceeding double bang (!!) check. 
const numberStrWithDoubleBang = !!myNumber && myNumber.toString();

// Here TypeScript cannot directly infer that myNumber is indeed truthy.
// To Typescript the function is a black box that takes in a number and
// returns a boolean, it does not know how the boolean value is calculated.
const numberStrWithFunction = isTruthy(myNumber) && myNumber.toString();

// While the isTruthy function above could easily have an implementation
// that does a simple truthy check on the passed in value as follows:
const isTruthy = <T>(value?: T | undefined) => !!value;

// It could also easily have an implementation with an output with no direct 
// connection to the truthy status of the passed in value such as:
const isTruthy = <T>(value?: T | undefined) => {
    const correctedValue = value ?? 0;
    const midPoint = correctedValue / 2;

    if (Math.random() * correctedValue > midPoint) {
        return true;
    }

    return false;
};

At this point in time inferrence of null | undefined state via a function is considered to complex or the performance impact would be to great.

NOTE: !!0 and Boolean(0) are both false as 0 is a falsy value, if you need to get the string representation of zero you will need to update your checks.

Related