Skip to content

Error when destructuring with literal initializers as fallback #26235

Description

TypeScript Version: 3.0.1

Destructuring with a fallback empty object literal is a common practice, especially in function arguments (which works), but not always:

Code

type Options = { color?: string; width?: number; };

function A({ color, width }: Options = {}) {
    //
}

function B(options: Options) {
    options = options || {};
    let { color, width } = options;
}

function C(options: Options) {
    let { color, width } = options || {};
}

Expected behavior: All three functions type-check and behave the same.

Actual behavior: "Initializer provides no value for this binding element" Error.

Which is simply incorrect, ES spec clearly defines the value when an object key is missing as undefined. Furthermore, I would argue TS should even accept let { color } = {}; though it's obviously not as common/important.

Related Issues:
Issue #4598 - Improved checking of destructuring with literal initializers fixed the same problem, but only for destructuring function arguments (example A).

Playground Link: here

Activity

  1. changed the title [-]Error when destructuring literals as fallback[/-] [+]Error when destructuring with literal initializers as fallback[/+] on Aug 6, 2018
  2. ghost added
    BugA bug in TypeScript
    on Aug 6, 2018
  3. RyanCavanaugh commented on Aug 8, 2018

    @RyanCavanaugh
    Member

    I would argue TS should even accept let { color } = {};

    Should we allow let { colour } = { color: "red" } (note the spelling mismatch)? If not, what's the distinguishing principle?

  4. zombie commented on Aug 9, 2018

    @zombie
    Author

    Fair point, I concede that I was making "a principled point", while I recognize the strength of TypeScript is often in making pragmatic choices.

  5. zombie commented on Aug 9, 2018

    @zombie
    Author

    Oops, wrong button.

  6. rambabusaravanan commented on Nov 24, 2018

    @rambabusaravanan

    So can I expect Typescript 3.2.1 will not report the below snippet

    let { subject, topic, subTopic } = value || {};

    as below error

    [ts] Initializer provides no value for this binding element and the binding element has no default value. [2525]

    reference: playground

    Since many es syntax are reported as error by typescript, we very restricted by the way we code 😞

  7. 7 remaining items

  8. kgregory commented on May 17, 2019

    @kgregory

    The issue seems to present itself when destructuring from a nested object as well

    interface Props {
      innerObject?: {
        name?: string;
        email?: string;
      };
    }
    
    // won't let name and email default to undefined
    // Initializer provides no value for this binding element and the binding element has no default value. 
    export const nestedDestructure1 = (props: Props) => {
      const { innerObject: { name, email } = {} } = props;
      console.log(name, email);
    };
    
    // no errors, but is a little annoying
    export const nestedDestructure2 = (props: Props) => {
      const { innerObject: { name = undefined, email = undefined } = {} } = props;
      console.log(name, email);
    };
    
    // no errors, but is a little annoying
    export const nestedDestructure3 = (props: Props) => {
      const { innerObject = {} } = props;
      const { name, email } = innerObject;
      console.log(name, email);
    };

    I'd really expect name and email to be typed as string | undefined in the nestedDestructure1 example, but that isn't the case.

    This issue has been fixed for function params with #4598, so this would work in that situation:

    export const nestedDestructure1 = ({innerObject: {name, email} = {}}: Props) => {
      console.log(name, email);
    };

    playground
    codesandbox

  9. mihailik commented on May 23, 2019

    @mihailik
    Contributor

    Another simple obvious example how it's wrong:

    type State = {
      open?: boolean;
    }
    
    class MyComponent extends React.Component<{}, State> {
      render() {
        const { open } = this.state || {};
                ~~~~
                Initializer provides no value for this binding element
                and the binding element has no default value.
                TS2525
    
        return <span>{open}</span>;    
      }
    }

    Destructuring from **** || {} is a very common pattern, and if TS can't simply fail on it.

    Even if there's deep theoretical correctness reasoning how and why it's consistent with {whatever}, this pattern is common enough to have a hard-coded special casing.

    Ryan Cavanaugh (@RyanCavanaugh) brought up this example:

    let { colour } = { color: "red" };
          ~~~~~~ Inconsistent British spelling of colour

    Which basically says TypeScript helpfully highlights undeclared members for fear of typos, which is totally reasonable!

    However, when destructuring from {}, there is no risk of typos. The actual error in this case protects against misspelling of a field that DOES NOT EXIST.

  10. fardad-dev commented on Nov 12, 2020

    @fardad-dev

    I have this problem with typescript 4.

  11. ignacio-nacho-barbano commented on Feb 4, 2021

    @ignacio-nacho-barbano

    #26235 (comment)

    I think this is the simplest solution, did the same on my project.

  12. k-funk commented on Mar 11, 2022

    @k-funk

    Ran into this when upgrading from TS 4.4 to 4.6.

    const errorCode = 99
    const { title, subtitle } = (() => {  // TS2525 on the deconstructed `subtitle` field
      switch (errorCode) {
        case 99:
          return {
            title: 'Account Not Found',
            subtitle: 'foo,
          }
        default:
          return {
            title: 'An unknown error occurred',
          }
      }
    })()
    

    I was able to resolve the TS2525: Initializer provides no value for this binding element and the binding element has no default value. error by explicitly returning subtitle: undefined:

        default:
          return {
            title: 'An unknown error occurred',
            subtitle: undefined,
          }
    

    Seems like a bug to me.

  13. xsjcTony commented on May 10, 2023

    @xsjcTony

    The issue seems to present itself when destructuring from a nested object as well

    interface Props {
      innerObject?: {
        name?: string;
        email?: string;
      };
    }
    
    // won't let name and email default to undefined
    // Initializer provides no value for this binding element and the binding element has no default value. 
    export const nestedDestructure1 = (props: Props) => {
      const { innerObject: { name, email } = {} } = props;
      console.log(name, email);
    };
    
    // no errors, but is a little annoying
    export const nestedDestructure2 = (props: Props) => {
      const { innerObject: { name = undefined, email = undefined } = {} } = props;
      console.log(name, email);
    };
    
    // no errors, but is a little annoying
    export const nestedDestructure3 = (props: Props) => {
      const { innerObject = {} } = props;
      const { name, email } = innerObject;
      console.log(name, email);
    };

    I'd really expect name and email to be typed as string | undefined in the nestedDestructure1 example, but that isn't the case.

    This issue has been fixed for function params with #4598, so this would work in that situation:

    export const nestedDestructure1 = ({innerObject: {name, email} = {}}: Props) => {
      console.log(name, email);
    };

    playground codesandbox

    Still an issue in v5.0.4.
    Upvoting

  14. RyanCavanaugh commented on May 12, 2023

    @RyanCavanaugh
    Member

    Please log a new bug if you think you have found an issue; maintainers are not necessarily reading comments on 5-year-old closed issues

  15. locked as resolved and limited conversation to collaborators on May 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptFixedA PR has been merged for this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions