Skip to content

Destructuring causes error for null/undefined properties #6784

Closed

Description

The following code:

interface Foo {
    x: number;
}

interface Bar {
    foo?: Foo;
}

function test( { foo: { x } }: Bar ) {
    alert( x );
}

test( {} ); // TypeError: Cannot read property 'x' of undefined

transpiles to:

function test(_a) {
    var x = _a.foo.x;
    alert(x);
}

instead of the (arguably) preferable:

function test(_a) {
    var x;
    if (_a.foo) x = _a.foo.x;
    alert(x);
}

The transpiled code does not check for the presence of optional property foo in the Bar interface. This leads to an error at invocation.

I can see three possible solutions:

  1. Always check for member existence when transpiling destructuring statements.
  2. Check for member existence only when an element is optional in the interface or type declaration.
  3. New syntax for destructuring that can indicate members should be checked.

E.g.

function test( { foo?: { x } }: Bar ) {}

Activity

  1. added
    By DesignDeprecated - use "Working as Intended" or "Design Limitation" instead
    SuggestionAn idea for TypeScript
    and removed
    By DesignDeprecated - use "Working as Intended" or "Design Limitation" instead
    on Feb 1, 2016
  2. RyanCavanaugh commented on Feb 1, 2016

    @RyanCavanaugh
    Member

    This is just the ES6 destructuring behavior; we don't use type system data to change the runtime behavior.

    That said, it's a bad idea to use a property of an optional property as part of a destructuring, and we should probably consider this an error.

  3. added this to the milestone on Feb 1, 2016
  4. RyanCavanaugh commented on Feb 1, 2016

    @RyanCavanaugh
    Member

    Accepting PRs. Any property destructuring must be non-optional or have a default

  5. Arnavion commented on Feb 2, 2016

    @Arnavion

    Ryan Cavanaugh (@RyanCavanaugh) Just to be clear, are you accepting PRs to make { foo: { x } }: Bar (where Bar.foo is optional) an error? Or to add support for { foo?: { x } }: Bar with the emit error (@errorx666) is asking for?

    Edit: Never mind, I see from the linked issue that it's the former.

  6. falsandtru commented on Feb 2, 2016

    @falsandtru
    Contributor

    I already said about this problem in #6125. This issue is duplicate.

  7. mhegazy commented on Feb 6, 2016

    @mhegazy
    Contributor

    we are accepting PRs for flagging destructuring into a optional property as an error. We still do not want to do any type directed emit.

  8. jods4 commented on Mar 24, 2016

    @jods4

    This implies that in cases where I know (because of program context) that the optional property is present in my object, I won't be able to destructure?
    Even though that would be valid, working JS?

    interface X { a?: { b: number } };
    let x: X;
    
    if (x.a) { // or some other domain rule that guarantees me 'a' is defined
      let { a: { b } } = x; // error: "a" is optional ?
    }

    I don't have any use for such code, just pointing out the possible implications if it becomes an error.

  9. 10 remaining items

  10. jods4 commented on Mar 13, 2017

    @jods4

    Andy (Andrewkraft) (@Andy-MS) can we have a syntax to indicate something nullable is actually set and so the error should be ignored? See my previous comment #6784 (comment)

  11. CzBuCHi commented on Jun 5, 2017

    @CzBuCHi

    what about doing it same way as c# ?. operator? some random blog with explanation

    var property  = some?.nested?.property?.name;
    // with is equivallent to:
    var property = some == null ? null
        : some.nested == null ? null
            : some.nested.property == null ? null
                : some.nested.property.name;

    in ts this would be current syntax

    var some: { nested?: { property?: { name: string } } } = ...;
    var { nested: { property: { name } } } = some; // name has type string | undefined

    transpiled into:

    var property = ['nested', 'property', 'name'].reduce((obj, prop) => obj && obj[prop], some);

    not sure how to handle multiple nested variables ...

  12. danielbodart commented on Feb 14, 2018

    @danielbodart

    What's the current status of this?

  13. schmaluk commented on Apr 21, 2018

    @schmaluk
    const [a, b] : string[] = [];
    const d: string = b.toUpperCase();
    

    In strict-mode should be invalid. Why only at runtime I get aTypeError: Cannot read property 'toUpperCase' of undefined ?

    Thought in strict-mode it is guaranteed that values are not null or undefined implicitly :(((
    I think it would be great if this can be fixed,
    A proper destructuring should handle all cases recursively like an "empty array" somehow...
    Maybe by recursion.

  14. sroze commented on Oct 13, 2019

    @sroze

    This code will dramatically fail because destructuring an undefined value seems to be allowed:

    const f1 = (something: string): { foo: string } | undefined => {
      return something == 'foo' ? { foo: 'bar' } : undefined;
    };
    
    const { foo } = f1('bar');

    Shouldn't TypeScript throw an error if it's the case? Happy to put my hands in TS' code if we have agreement on this expected behaviour :)

  15. kitsonk commented on Oct 14, 2019

    @kitsonk
    Contributor

    Samuel ROZE (@sroze) it does error if you have strict null checks on. When you run in sloppy mode, it behaves as expected and does not error.

  16. prajun7 commented on Apr 13, 2022

    @prajun7

    Ran into the same problem. Any fixes?

  17. RyanCavanaugh commented on Oct 23, 2025

    @RyanCavanaugh
    Member

    This works under strictNullChecks, which there's no excuse to not have on at this point

  18. drago1520 commented on Nov 21, 2025

    @drago1520

    The issue is not fixed.

    @sroze it does Error if you have strict null checks on. When you run in sloppy mode, it behaves as expected and does not error.

    The issue is not fixed. When I try to destructure an object in an array, it doesn't throw a compile-time error, but the code explodes at runtime with a TypeError. Even with everything strict

  19. RyanCavanaugh commented on Nov 24, 2025

    @RyanCavanaugh
    Member

    drago1520 you're missing noUncheckedIndexedAccess

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions