Don
Don

Reputation: 754

Typescript and React defaultProps are still being treated as possibly undefined

interface PageProps {
    foo?: Function;
    bar: number;
}

export class PageComponent extends React.Component<PageProps, {}> {
    public static defaultProps: Partial<PageProps> = {
        foo: () => alert('Did foo')
    };

    private doFoo() {
        this.props.foo(); // Typescript Error: Object is possibly 'undefined'
    }

    public render(): JSX.Element {
        return (
            <div>
                <span>Hello, world! The number is {this.props.bar}</span>
                <button onClick={() => this.doFoo()}>Do the Foo</button>
            </div>
        );
    }
}

Is there a way to tell Typescript that props.foo will always be defined?

There is a very good SO question and answer that discusses how to properly define the types for props on a component. It even discusses how you would let TS know about defaultProps on a stateless component.

However Typescript will still complain inside a regular component definition that your props might be undefined (as illustrated in my example above).

You can call it using a bang (this.props.foo!()) and that prevents TS from throwing an error, but it also prevents any kind of type checking on whatever you've passed in. In the example above, not a big deal. In more complex code, it has already bitten me.

So I am currently implementing things like this instead:

private doFoo() {
    if (this.props.foo) this.props.foo();
}

That has one drawback, which I think is rather important: It makes my code a little confusing. In reality, I always want to call this.props.foo(), but it seems like, without investigating the class more closely, there are cases where nothing will happen inside the doFoo method. (Yes I could add a comment explaining that the if is only for Typescript to be happy, but I'd rather my code speak for itself when it can.)

My code becomes even more unnecessarily complex when I actually need to return something from this.props.foo().

I am currently using @types/react for my type definitions.

Upvotes: 8

Views: 5217

Answers (1)

Poul Kruijt
Poul Kruijt

Reputation: 71891

So, the compiler is saying that the object is possibly undefined. You know better that it's not, so you just want to write this.props.foo(). The right way to do it is using the null assertion type operator

this.props.foo!()

If you say that this has bit you before, then that's your own fault for not checking it :), which means the compiler was -right- that the object could have been undefined. And if there is a possibility it could be undefined, you should always check it. Perhaps you like a more shorter version of the if statement? It's one character less:

private doFoo() {
  this.props.foo && this.props.foo();
}

or with the typescript >= 3.8:

private doFoo() {
  this.props.foo?.();
}

On the other hand you can make an extra interface which extends the PageProps. For the fun of it, let's call it PagePropsFilled:

interface PagePropsFilled extends PageProps {
    foo: Function;
}

You can now set your this.props to PagePropsFilled, and it won't show you an error anymore

Upvotes: 7

Related Questions