In TypeScript, no errors are thrown when accessing a property of an undefined object

const testResult = config.layers?.find((layer) => layer.id === 'blah')?.dataSource!;
console.log('testResult: ', testResult);

I have an object named Configuration (referred to as "config"), which contains an array of Layers.

Each layer is assigned an "id".

Despite not having an id labeled "blah", why does the output show testResult: undefined?

I would have anticipated a runtime error instead.

As far as I know, the "!" symbol indicates that the variable cannot be undefined. However, the .find() method returned an undefined object, making ".dataSource" inaccessible.

This situation has left me baffled!

Could there be a configuration setting causing this unexpected behavior?

Below is my tsconfig.json file:

{
    "extends": [
        "eslint:recommended",
        "google",
        "react-app",
        "plugin:jsx-a11y/recommended",
        "plugin:react/recommended",
        "plugin:react/jsx-runtime",
        "plugin:@typescript-eslint/eslint-recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:prettier/recommended",
        "plugin:jest/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "2018",
        "sourceType": "module",
        "project": ["tsconfig.test.json"]
    },
    "plugins": ["react", "jsx-a11y", "@typescript-eslint", "prettier", "jest"],
    "rules": {
        "@typescript-eslint/no-unused-vars": "error",
        "@typescript-eslint/no-explicit-any": "error",
        "@typescript-eslint/no-empty-interface": "off",
        "require-jsdoc": [
            "error",
            {
                "require": {
                    "FunctionDeclaration": false,
                    "MethodDefinition": false,
                    "ClassDeclaration": false,
                    "ArrowFunctionExpression": false,
                    "FunctionExpression": false
                }
            }
        ],
        "jest/no-disabled-tests": "warn",
        "jest/no-focused-tests": "error",
        "jest/no-identical-title": "error",
        "jest/prefer-to-have-length": "warn",
        "jest/valid-expect": "error"
    }
}

Answer №1

Your mistake is not related to TypeScript typing, but rather standard JavaScript behavior.

By using optional chaining, you instruct JavaScript to verify the value on the left of the chain. If it is null or undefined, the operation on the right will be bypassed. Consider the following example:

const foo = {
  a: 'abc',
  b: null,
};

console.log(foo.b.length);
console.log(foo.b?.length);

The first console log will result in an error because accessing `length` on a null value like `foo.b` is not allowed. However, the second log will output "null" since `b?.length` means "Access `length` only if `b` is not null, otherwise return null."

Therefore, in this code snippet:

config.layers?.find(...)?.dataSource!

You are assuming that `dataSource` is non-null, despite having optional chaining for both `layers` and `find(...)`. If either of those expressions returns null or undefined, the execution stops at that point, and `dataSource!` is never reached.

If you wish to apply the non-null assertion to the entire line, wrap the entire expression in parentheses:

(config.layers?.find(...)?.dataSource)!

However, at this stage, you should be absolutely certain about your actions as you are essentially disregarding TypeScript's compile-time null safety feature.

Answer №2

It appears that there is some confusion regarding the purpose of the ! symbol in TypeScript. This symbol instructs the TS language server to disregard any potential for the variable dataSource being undefined.

Furthermore, it seems there may be a misunderstanding about how TypeScript operates - errors will not be thrown because TypeScript ultimately compiles down to plain JavaScript when executed. TypeScript primarily serves to assist in writing more reliable and robust code, without altering the runtime behavior.

When utilizing the ! symbol, you are essentially declaring: "I am certain that dataSource does exist", thereby signaling to the IDE to suppress any related errors. This operator effectively carries out a non-null assertion

However, if it turns out that dataSource is indeed undefined, this could be due to either the variable itself being undefined or the find() method returning nothing.

Answer №3

The rationale behind this behavior lies in the use of Optional Chaining (?).

const testResult = config.layers?.find((layer) => layer.id === 'blah')?.dataSource!;

If config.layers is undefined, the find method won't be invoked on it, and JavaScript will simply default to testResult, which will be undefined.

This cascades down the chain as well, meaning that if find doesn't return anything, then the dataSource property remains untouched.

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

What is the term for specifying a variable's data type using a set of values instead of a traditional type?

Recently, I stumbled upon some code that introduces a class with specific variables defined in an unconventional manner. export class Foo { id: string = "A regular string" bar: '>' | '<' | '=' | '<=' | ...

Converting JSON into TypeScript class or interface?

Currently, I am in the process of building a web application using Angular2 and Typescript. The JSON structure I have is as follows: { "homeMenu":{ "aname1":{ "title":"text", "route":"myroute" }, "aname2":{ "title":"tex ...

Type of Multiple TypeScript Variables

Within my React component props, I am receiving data of the same type but with different variables. Is there a way to define all the type variables in just one line? interface IcarouselProps { img1: string img2: string img3: string img4: string ...

Tips for using jest toHaveBeenCalled with multiple instances

Currently, I am in the process of writing a test case for one of my functions. This function calls another function from a library, and I am attempting to mock this function (saveCall). Below is a snippet of the sample code in question: import { Call } fro ...

Encountering problem with '@datadog/browser-rum' compilation related to the 'allowedTracingOrigins' attribute

I'm facing a typing problem with the @datadog/browser-rum library: Error: node_modules/@datadog/browser-rum-core/src/domain/configuration.ts:100:3 error TS2322: Type '{ applicationId: string; version: string; actionNameAttribute: string; premium ...

What is the method to group a TypeScript array based on a key from an object within the array?

I am dealing with an array called products that requires grouping based on the Product._shop_id. export class Product { _id: string; _shop_id: string; } export class Variant { variant_id: string; } export interface ShoppingCart { Variant: ...

Error: Attempting to access a property called 'sign' on an undefined value

I encountered an issue while signing my transaction where I received an error message stating sendTransaction needs signer. Even though both message (encrypted using keccak256) and signer have values, I am unsure why there is a problem when executing the w ...

The concept of inheritance in CommonJS using Typescript

While experimenting with Ts, I encountered a roadblock "use strict"; declare const require: any; const EventEmitter : any = require('events').EventEmitter; class Foo extends EventEmitter{ //*error* Type 'any' is not a constructor fu ...

Unable to convert JSON response into an array while using Angular 2

I'm currently working on converting a JSON response into an array within Angular 2. Below is the code I have implemented: .ts file response; resp; constructor(private http:Http) { } get() { this.http.get("http://localhost:3000/items") .map(res=>r ...

Managing data with Angular 2: Setting and retrieving values

In my current project, I am working on developing a service that can parse data to different components based on various routes. When I call this service within the same component, everything works as expected and I get the desired results. However, when ...

Discover the key to ensuring that optional peer dependencies are genuinely optional

My NPM package offers both core functionality in the form of React hooks and UI components that utilize these core features. Originally, I planned to create two separate packages - one for the core functionality and another for the components. However, upo ...

Error message: The database query function is encountering an issue where the property 'relation.referencedTable' is undefined and cannot be accessed

Currently, I am working with two schemas named products.ts and category.ts. The relationship between these files is defined as one-to-many. In the products.ts file: import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; import ...

Transferring data from the server to the client side in Next JS with the help of getInitialProps

I am currently developing an application using nextJS. Within server/index.ts, I have the following code: expressApp.get('/', (req: express.Request, res: express.Response) => { const parsedUrl = parse(req.url, true); const { query } = ...

"An issue arises as the variable this.results.rulesFired is not properly

I am faced with a set of table rows <tr *ngFor="let firedRule of splitRules(results.rulesFired); let rowNumber = index" [class.added]="isAdd(firedRule)" [class.removed]="isRemove(firedRule)" ...

Updating non-data properties dynamically in a custom AG Grid cell renderer

In my grid setup, I have implemented an editor button in a column for each row and a new item creator button outside the grid. One of the requirements is that all buttons should be disabled when either the create or edit button is clicked. To achieve thi ...

Handling functions in Ant Design Select component with TypeScript types

I have a query. Antd offers a custom Select input with functions like onSelect, onChange, etc. I am utilizing the onSelect function which requires the following arguments: (JSX attribute) onSelect?: ((value: string | number | LabeledValue, option: OptionDa ...

Error: Unable to access to the 'title' property of a null value

I am facing an issue with retrieving the title and lastname from a token after decoding it. Despite my efforts, I keep getting an error message that says "cannot read property title of null". If anyone can assist me with this problem, I would greatly app ...

Issues arise as a result of conflicts between the dependencies of @ionic/angular, Angular 13, typescript,

Current Environment Details: ionic info Ionic: Ionic CLI : 6.18.1 (/usr/local/lib/node_modules/@ionic/cli) Ionic Framework : @ionic/angular 5.8.5 @angular-devkit/build-angular : 13.0.2 @angular-devkit/schemat ...

The inferred type of a TypeScript promise resolved incorrectly by using the output value from a callback function

Although some sections of the code pertain to AmCharts, the primary focus of the question is related to TypeScript itself. The JavaScript functions within the AmCharts library perform the following tasks: export function createDeferred(callback, scope) { ...

Simplify if statements by eliminating repetition

I have been tasked with refactoring the code below and I have already done so (check the image for reference). However, my supervisor is still not satisfied with the changes, LOL. const { appTargetId, appUserTargetId, appUserId } = buildIndexKeys(input); ...