51

I have a TypeScript interface with some optional fields and a variable of that type:

interface Foo {
    config?: {
        longFieldName?: string;
    }
}

declare let f: Foo;

I'd like to put longFieldName in a variable of the same name.

If config weren't optional, I'd use destructuring assignment to do this without repeating longFieldName. But it is, so I get a type error:

const { longFieldName } = f.config;
     // ~~~~~~~~~~~~~  Property 'longFieldName' does not exist on type '{ longFieldName?: string | undefined; } | undefined'.

I can use optional chaining to concisely handle the undefined case:

const longFieldName = f?.config.longFieldName;  // OK, type is string | undefined

But now I have to repeat longFieldName.

Can I have my cake and eat it, too? Can I use optional chaining to handle the undefined case without repeating longFieldName? If not, what is the most concise/idiomatic workaround? See playground link.

danvk
  • 15,863
  • 5
  • 72
  • 116
  • Currently is out of scope for ecma t39: https://github.com/tc39/proposal-optional-chaining/issues/74 – llobet Dec 10 '20 at 16:17

2 Answers2

47

Use short circuit evaluation to get a fallback value (empty object) if the f?.config expression evaluate to undefined:

const { longFieldName } = f?.config || {};
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • That's the workaround. But is there something more akin to optional chaining? (I don't think you want the `...` before `longFieldName`.) – danvk Nov 14 '19 at 21:35
  • The `...` was redundant. As far as I know, optional chaining doesn't handle the fallback. The `...` was a leftover from another solution - spread `f?.config` into an object `{ ...f?.config }`. If the chaining evaluates to `undefined` or `null`, they will be ignored by the spread. – Ori Drori Nov 14 '19 at 21:40
  • This doesn't work for me. I get `undefined is not an object (evaluating '_ref2.longFieldName')`. – GollyJer Feb 07 '20 at 00:14
  • For one property only on right side, this can be done too: `const { longFieldName = "" } = f ?? {}`, with optional (as I inserted) fallback value – Alexander Cerutti Apr 27 '20 at 09:58
  • This is what I usually do, but it bothers me and I wish ECMA could adopt `const { longFieldName } = f?.config?;` https://github.com/tc39/proposal-optional-chaining/issues/74 – JHH Jan 26 '22 at 08:08
  • This workaround will not work when you are destructuring more than one level deep. For instance, `const {longFieldName: {subField}} = f?.config || {} // Uncaught TypeError`. See the better answer by @wayne-li. – Arel May 08 '23 at 21:42
9

You can use the default values.

const Foo = {}
const { config: { longFieldName = "" } = {} } = Foo
console.log(longFieldName) //''
Wayne Li
  • 411
  • 1
  • 6
  • 16
  • 1
    This does work if `config` is missing, but if you add another intermediate level that is also missing it will not work. – Tom Boutell Jan 07 '21 at 22:59
  • 1
    @TomBoutell this does work with intermediate levels. You just have to set their default as well with `= {}`. For instance: `let {config: {longFieldName: {subField = 123} = {}} = {}} = {}` – Arel May 08 '23 at 21:46