mpen
mpen

Reputation: 282805

Why is TS complaining that object is possibly null when I explicitly wrapped it in an `if`?

Here's my code:

import React from 'react';

export default class ErrorBoundary extends React.Component {

    state = {error: null, errorInfo: null};

    componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
        this.setState({
            error: error,
            errorInfo: errorInfo
        })
    }

    render() {
        if (this.state.errorInfo) {
            return (
                <div>
                    <h2>Something went wrong.</h2>
                    <pre>
                        <code>
                            {String(this.state.error)}
                            {this.state.errorInfo.componentStack} // <-- error is here
                        </code>
                    </pre>
                </div>
            );
        }

        return this.props.children;
    }
}

Message is:

ERROR in [at-loader] ./src/components/ErrorBoundary.tsx:22:30 TS2531: Object is possibly 'null'.

enter image description here

But the if block won't execute if this.state.errorInfo is null, so I'm not sure what the issue is.

Why am I getting this error and how do I fix it?

Even if I write it like this:

 {this.state.errorInfo !== null ? this.state.errorInfo.componentStack : 'hello'}

or

 {this.state && this.state.errorInfo ? this.state.errorInfo.componentStack : 'hello'}

I get the same error.


tsconfig for good measure:

{
    "compilerOptions": {
        "strict": true,
        "importHelpers": false,
        "inlineSources": true,
        "noEmitOnError": true,
        "pretty": true,
        "module": "ES6",
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": false,
        "removeComments": true,
        "preserveConstEnums": false,
        "sourceMap": true,
        "lib": ["es2018","dom"],
        "skipLibCheck": true,
        "outDir": "dist",
        "target": "es2018",
        "declaration": true,
        "resolveJsonModule": false,
        "esModuleInterop": false,
        "jsx": "react",
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true
    },
    "files": [
        "src/index.tsx"
    ],
    "include": [
        "src/types/**/*.d.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

Upvotes: 5

Views: 9530

Answers (3)

maxpaj
maxpaj

Reputation: 6781

I was still seeing this issue when using a state variable inside a map.

render() {
    if (!this.state.foo) { return <Loading /> }

    [1,2,3].map(n => {
        this.state.foo.property; // Object possibly null
    }

    ...
}

I solved this by:

render() {
    if (!this.state.foo) { return <Loading /> }

    const foo = this.state.foo;

    [1,2,3].map(n => {
        foo.property; // Cool!
    }
    
    ...
}

Upvotes: 0

Amir
Amir

Reputation: 341

state update is asynchronous and u shouldnt rely on it to be updated before rendering.

you should read more about React component lifecycle

if u dont wanna use redux (this is also debatable - redux is complex and u should use it only when u need it, but state manipulation is not easy so u should use it anyway)

with that said... just simply perform a conditional check before u read from it (again async)

constructor(props) {
  super(props);
  this.state = {init: here};
}

You should not call setState() in the constructor(). Instead, if your component needs to use local state, assign the initial state to this.state directly in the constructor.

Constructor is the only place where you should assign this.state directly. In all other methods, you need to use this.setState() instead.

Upvotes: -2

jbialobr
jbialobr

Reputation: 1520

It seems that

state = {error: null, errorInfo: null};

overrides the state's type. The inferred type of errorInfo is always null You can correct it by giving the type explicitly:

state: { error: Error | null, errorInfo: React.ErrorInfo | null } =
{ error: null, errorInfo: null };

This issue is reported and discussed here https://github.com/Microsoft/TypeScript/issues/10570

Upvotes: 8

Related Questions