Error in Typescript occurrence when combining multiple optional types

This code snippet illustrates a common error:

interface Block {
  id: string;
}

interface TitleBlock extends Block  {
  data: {
    text: "hi",
    icon: "hi-icon"
  }
}

interface SubtitleBlock extends Block  {
  data: {
    text: "goodbye",
  }
}

interface CommentBlock extends Block {
  author: string; 
}

type BlockTypes = TitleBlock | SubtitleBlock | CommentBlock

function mapper(block : BlockTypes) {
    if(block?.data?.text){  // TypeScript Error occurs here!!! 
      /** perform an action */
    }
}

Live example

I am trying to execute something in the function only when the block has a specific attribute, but TypeScript doesn't permit it even though there are types within BlockTypes that possess that property.

What would be the most effective method of handling this type of functionality?

Answer №1

The optional chaining operator (?.) offers protection against null and undefined values, but it does not narrow down a union-typed expression to only those members that are known to contain a specific key. For more information, refer to this comment in Microsoft/TypeScript#33736.


One issue arises from the fact that object types in TypeScript are open and can have properties beyond what the compiler recognizes. Even if the property block.data is truthy, it doesn't guarantee the existence of block.data.text:

const oof = {
  id: "x",
  author: "weirdo",
  data: 123
}
mapper(oof); // approved

In this case, oof qualifies as a valid CommentBlock, allowing mapper(oof) without error. Despite oof.data being truthy, oof.data.text resolves to undefined. This situation could lead to issues if you try to treat it as a string.

Therefore, enabling optional chaining to filter unions as desired would be unsound and lacking type safety.


A workaround would involve using the in operator as a type guard, especially when scenarios like oof are rare:

function mapper(block: BlockTypes) {
  if ("data" in block) {
    block.data.text.toUpperCase()
  }
}

This approach eliminates compiler errors but remains unsound since runtime errors may still occur with mapper(oof). Proceed cautiously.

It's interesting that TypeScript permits 'in' to function this way while limiting similar constructs. To understand why this decision was made, check out the discussion on microsoft/TypeScript#10485. The TypeScript team's rationale may shed light on this choice despite potential misuse of other constructs like bar.foo && bar.foo.baz. The team believes such constructs are less prone to mistakes based on their observations. 🤷‍♂️

Explore the code on Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can you deactivate all form elements in HTML except for the Submit button?

Is there a method available to automatically deactivate all form elements except the submit button as soon as the form loads? This would entail pre-loading data from the backend onto a JSP page while restricting user access for editing. Users will only be ...

Silky animations in kinetic.js (html5 canvas)

I am seeking a better grasp on kinetic.js animation. Recently, I came across a tutorial at . After experimenting with the code provided, I managed to create an animation that positions my rectangle at x coordinate 100. However, I am struggling to achieve s ...

Working with JSON data in AngularJS2's templates

Is there a way for me to process JSON from the template in a manner similar to the second code I provided? First code. This method works well when using .json and .map () @Component({ ..// template: `..// <li *ngFor="#user of users"> ...

How to modify the color of a div element with jQuery?

I need some help changing the color of a div with 'jQuery', but I am not sure how to do it. Below is the code I have been trying: function updateDivColor() { $('#OutlineX1').css("color","blue"); } ...

The ineffective operation of LoopBack ACL

I have created a custom model called MyUser which inherits from the LoopBack User model. In my boot script, I created a user in the MyUser model, a custom role, and mapped that role to it. However, the ACL (Access Control List) is not working properly afte ...

The GitHub-hosted package encounters a failure during the npm publish process

Last week everything was running smoothly, but now I am encountering an error and not sure what went wrong. (Apologies for the formatting as there are many lines of log) Running Node version: v10.15.3 Using npm version: 6.4.1 After executing npm publish ...

Discovering the magic of activating a JavaScript function on jQuery hover

I need to call a JavaScript function when hovering over an li element. var Divhtml='<div>hover</div>'; $('li a').hover(function(){ $(this).html(Divhtml); //I want to trigger hovercall(); wh ...

React js select all checkbox implementationIs this what you need? Let

I am working on creating a select-all checkbox feature where each product row has a checkbox. When a checkbox is clicked, it should capture the product id, variations, and count to calculate and display the total price of the selected products. I have su ...

Tips for avoiding the default rendering of Nuxt 3 layout?

After reviewing the Nuxt 3 documentation and finding it lacking in explanation, I turned to the Nuxt 2 docs. According to them, the default layout should be replaced by a specific layout specified within the name property of the <nuxt-layout> compone ...

Having trouble adjusting the label fontSize in ReactJS when using semantic-ui-react?

Is there a way to decrease the size of the label for a form input? In this scenario, attempting to set the fontSize does not work: <Form.Input label="Username" style={{fontSize: '10px'}} /> Does anyone have any suggestions on how to res ...

The jqGrid is not showing the AJAX JSON data as expected

Currently, I am exploring jqgrid and trying to figure out how to display data in another jqgrid when clicking on a specific colmodel. The retrieved data from the server goes through a function and reaches the grid, but no information is displayed without a ...

Slight Misalignment of Elements

I am attempting to align two corners of an element so that they perfectly match the corners of another element. In simpler terms, I am aiming to synchronize the corners of one element with those of another element. Below is the code snippet: ... When y ...

What is the reason for the viewport in three.js renderer not functioning properly on IE browser?

Try setting the viewport with different coordinates: this.renderer.setViewport(50, -50, this.width, this.height); What could be causing issues with IE browser compatibility? ...

PHP Calculator - Displaying results in both the webpage and an Ajax popup for enhanced user experience

UPDATE - the issue was resolved by incorporating $('.popup-with-form').magnificPopup('open'); into the StateChanged function in the JavaScript. Many thanks to @Bollis for the assistance. The Initial Query: I have a basic calculator lo ...

Identifying Gmail line breaks in the clipboard using Angular

I'm currently working on a feature that allows users to paste content from Gmail into a field and detect line breaks. The field doesn't have to be a text area, I just need to identify the line breaks. However, there seems to be an issue with det ...

The element will only show up upon resizing (in Safari web browser)

I am facing a problem with a button styled using the class btn-getInfo-Ok <button class="btn-getInfo-Ok">Hello</button> in my style.css file .btn-getInfo-Ok { color:white !important; margin: 0 auto !important; height:50px; bottom:0; ...

Creating markers for every value in a table using Google Maps API v3

Looking for some guidance here. I have a table with multiple values that I need to process using a function. Could someone help me with a suitable loop in jQuery or JavaScript that can achieve this? I'm still learning the ropes of these languages. My ...

Assign a unique HTML attribute to a child element within a Material-UI component

Currently, I am trying to include the custom HTML5 attribute "data-metrics" in the span element within the ListItemText Material UI component. However, I am facing some difficulty achieving this as per the specifications outlined in the Component API Docum ...

Having trouble implementing types with typescript in Vue-toastification for vuejs 3

Having trouble incorporating vue-toast-notification into my vue 3 project. The issue seems to be with vue Augmenting Types. I've tried other solutions without success, encountering the following error: TS2339: Property '$toast' does not exis ...

Creating both Uniform and Varying drawings on a single webGL canvas

My goal is to create this specific illustration. https://i.sstatic.net/5AfdW.png This project requires the usage of TypeScript. The Code: The code is organized across multiple files. Within the scenegraph file, there's a function that visits a gro ...