A step-by-step guide to resolving the TS2345 compilation error during your TypeScript upgrade from version 4.7 to 4.8

We encountered a compiling error after upgrading from TypeScript 4.7.4 to a newer version (specifically, 4.8.4). This error was not present in our codebase when using 4.7.4.

To pinpoint the issue, I have extracted the error into a small code snippet. When compiling with 4.8.4 or any subsequent versions, we receive an error message similar to this:

src/example2.ts:19:62 - error TS2345: Argument of type 'object' is not assignable to parameter of type 'Record<string, BasicValue>'.
  Index signature for type 'string' is missing in type '{}'.

19         typeof value === 'object' && areFieldsValid(PFields, value, isValidPField);
                                                                ~~~~~

This issue seems to align with a potential breaking change outlined in the TypeScript 4.8 release notes available at

The sample code causing this error includes:

export interface PNode { 
    'text'?: string
}

export const PFields = ['text'];

export type DefinedBasicValue = number | boolean | string | Array<BasicValue> | {} | {
    [key: string]: BasicValue
    [key: number]: BasicValue
}

export type BasicValue = undefined | DefinedBasicValue


/**
 * Take any object, value, undefined, or null, and determine if it is a PNode
 */
export const isPNode = (value?: {}): value is PNode =>
        typeof value === 'object' && areFieldsValid(PFields, value, isValidPField)

export function areFieldsValid(fields: string[], value: Record<string, BasicValue>, ...validations: ((field: string, value: BasicValue) => boolean)[]): boolean {
    return true
}

export const isValidPField = (field: string, value: BasicValue): boolean => true

The root of the problem lies in the value argument used in the function call on line 19:

areFieldsValid(PFields, value, isValidPField)
.

We seek assistance in understanding why this behavior exists in recent TypeScript versions but not in older ones. Moreover, what would be the most appropriate way to address such errors without resorting to workarounds?

Playground with v4.7.4 demonstrating no error occurrences

Playground illustrating v4.8.4 error manifestation

Playground showcasing current version (v5.4.5) indicating the persisting error

Answer №1

Explanation

During the update to TypeScript 4.8, significant changes were made to control flow analysis and how the {} type is handled. Two key changes are responsible for the observed behavior:

  1. The intersection of T & {} now simplifies to just T. This optimization is evident in scenarios like the redefinition of NonNullable as
    type NonNullable<T> = T & {};
    , which enables the simplification
    NonNullable<NonNullable<T>> == NonNullable<T>
    .
  2. The comparison typeof x === "object" intersects the current type of x with object | null.

When value is of type {} | undefined, the code typeof value === 'object' results in

({} | undefined) & (object | null)
, ultimately simplifying to object (undefined and null are removed, leaving {} & object)

function foo(x?: {}) {
    x; // {} | undefined
    if (typeof x === "object") {
        x; // object
    }
}

Playground Link

This presents a challenge because object cannot be assigned to any of the Record types. This discrepancy arises from the fact that object denotes a "non-primitive value," a broader classification compared to what Record<TKey, TValue> defines as an object with enumerable key-value pairs—that is, essentially a map or dictionary structure. Consequently, not all objects conform to this pattern, making safe "downcasting" impossible. For further insights on object, refer to Difference between 'object' ,{} and Object in TypeScript

Answer №2

VLAZ provided an explanation regarding the changes in version 4.8. However, the typeguards demonstrated do not fully prevent isPNode from passing primitives to areFieldsValid, causing some discomfort.

The request is to call it with any value, including primitives (resulting in a return of false). This usage of any seems valid, but alternatively, unknown could also be utilized. The use of any here does not concern me since isPNode serves as a type predicate, likely used at a boundary between untyped and typed areas. If adherence to the "no explicit any" rule is required, or mandated by your workplace, utilizing unknown or {} | null with a type assertion would suffice.

any:

export const isPNode = (value?: any): value is PNode =>
    typeof value === 'object' &&
    !!value &&
    areFieldsValid(PFields, value, isValidPField);

Playground link

A snippet was added to filter out null, given that typeof null returns "object".

unknown:

export const isPNode = (value?: unknown): value is PNode =>
    typeof value === 'object' &&
    !!value &&
    areFieldsValid(PFields, value as Record<string, BasicValue>, isValidPField);

Playground link

Once again, note the && !!value for managing null. A type assertion may still be necessary, especially when working with unknown.

{} | null

Alternatively, value?: {} | null could be implemented, although it is akin to using value?: any. The same approach as the unknown solution can be employed:

export const isPNode = (value?: {} | null): value is PNode =>
    typeof value === 'object' &&
    !!value &&
    areFieldsValid(PFields, value as Record<string, BasicValue>, isValidPField);

Playground link

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

Simple steps to transform the "inputs" syntax into the "@Input" property decorator

There's this code snippet that I need to modify: @Component({ selector: 'control-messages', inputs: ['controlName: control'], template: `<div *ngIf="errorMessage !== null">{{errorMessage}}</div>` }) Is the ...

Tips for mocking a module with a slash character in its name?

When it comes to mocking a standard npm project, the process is simple. Just create a __mocks__ folder next to the node_modules folder, then name the file after the package and insert the mock contents. For example: /__mocks__/axios.ts However, I encount ...

I am looking to extract solely the numerical values

Programming Tools ・ react ・ typescript ・ yarn I am trying to extract only numbers using the match method But I keep encountering an error Error Message: TypeError: Cannot read property 'match' of undefined const age="19 years ...

Guide to setting up Cosmos DB database connection using NestJS version 9.0.0

I'm encountering issues when attempting to include the Cosmos DB connection module in nestjs v9, as I'm facing dependency errors. Nest is unable to resolve the dependencies of the AzureCosmosDbCoreModule (COSMOS_DB_CONNECTION_NAME, ?). Please ens ...

Creating a custom type for the parameter of an arrow function in Typescript

I need assistance defining the type for an object parameter in an arrow function in TypeScript. I am new to TypeScript and have not been able to find any examples illustrating this scenario. Here is my code: const audioElem = Array.from(videoElem.pare ...

Retrieve various data types through a function's optional parameter using TypeScript

Creating a custom usePromise function I have a requirement to create my own usePromise implementation. // if with filterKey(e.g `K=list`), fileNodes's type should be `FileNode` (i.e. T[K]) const [fileNodes, isOk] = usePromise( () => { ...

NativeScript Error Code NG8001: Element 'ActionBar' is unrecognized

In my project, the startupscreen module setup is as follows: import { NativeScriptFormsModule } from "@nativescript/angular"; import { NativeScriptCommonModule } from "@nativescript/angular/common"; import { NgModule, NO_ERRORS_SCHEMA } ...

Prevent using keys of nullable properties as method parameters in Typescript generics

What is the solution to disallow a method from accepting a parameter of type keyof this where the property is nullable? Consider the following example: abstract class MyAbstractClass { get<K extends keyof this>(key: K): this[K] { return this[k ...

Convert the generic primitive type to a string

Hello, I am trying to create a function that can determine the primitive type of an array. However, I am facing an issue and haven't been able to find a solution that fits my problem. Below is the function I have written: export function isGenericType ...

Optimal approach to configuring Spring Boot and Angular for seamless communication with Facebook Marketing API

Currently, I am working on a Spring Boot backend application and incorporating the Facebook marketing SDK. For the frontend, I am utilizing Angular 10. Whenever I create a new page or campaign, my goal is to send the corresponding object back to the fronte ...

I'm unable to import correctly using the --compiler option

Having an issue here. I'm trying to bring in the typescript compiler. I used this command: bit import bit.envs/compilers/typescript --compiler Unfortunately, it didn't work. This is the error message: bit import [ids...] import components in ...

Tips for setting variable values in Angular 7

I'm encountering an issue with assigning values to variables in my code. Can anyone provide assistance in finding a solution? Here is the snippet of my code: app.component.ts: public power:any; public ice:any; public cake:any; changeValue(prop, ...

Angular 6's observable variable doesn't properly support Ng If functionality

I successfully implemented server-side pagination in the Angular6 material data grid following the instructions from this link. Now, I am facing an issue where I want to display a "No Data Found" message if the response dataset is empty. I tried using ngI ...

The Vue $refs Object is classified as 'unidentified' in nature

I'm encountering an issue while attempting to utilize $refs in my Vue 3 application. Each time I try, I receive the Typescript error stating that "Object is of type 'unknown'". I am uncertain about how to resolve this problem. Here's ...

What is the best way to integrate retrieved data into Next.js with TypeScript?

Hello everyone! I recently started working with Next.js and TypeScript. Currently, I'm attempting to use the map() function on data fetched from JsonPlaceholder API. Here is my implementation for getStaticProps: export const getStaticProps: GetStatic ...

Blend multiple images using Angular

Is there a way to combine multiple images in Angular? I came across some HTML5 code that seemed like it could do the trick, but unfortunately, I couldn't make it work. <canvas id="canvas"></canvas> <script type="text/javascript"> ...

React and Typescript Multimap Approach

I'm a beginner in TypeScript and I am struggling to understand how to create a multimap. The code I have is shown below. My goal is to loop through the itemArray and organize the items based on their date values. I want to use the date as the key for ...

Tips for incorporating SectionList sections in React Native using an array

I am working with an array of objects named movies (const movies = movie[]). Each movie object includes properties like name, description, date and duration. movie: { name: string; description: string; date: Date; duration: number } My ...

Retrieving the Object value in Primeng p-dropdown when there is a change in selection

In my p-dropdown, I am trying to extract the selected value. <p-dropdown optionLabel="name" [options]="things" placeholder="Select Thing" [(ngModel)]="input" (onChange)="getValue(input)"></p-dropdown> typescript: //each lin ...

What is the best way to filter or choose tuples based on their inclusion in a certain group

I am working with a tuple object that contains nested tuples. const foo = [ { id: 't1', values: ['a', 'b'] }, { id: 't2', values: ['a', 'c'] }, { id: 't3', values: ['b', ...