Exploring the concept of indexing partial types in Typescript, utilizing react-hook-form to track dirty fields and update

When using react-hook-form, you can access the dirtyFields property which indicates updated fields with a value of true.

v = {
    username: 'alice',
    email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="84e5e8ede7e1c4e1fce5e9f4e8e1aae7ebe9">[email protected]</a>',
    role: 'USER'
}

dirtyFields = {
    username: true
}

In this scenario, the goal is to send only the updated fields (in this case, just the username).

An attempted solution:

type User = {
    username: string
    email: string
    role: 'ADMIN' | 'USER'
}

function submitForm (v: User, dirtyFields: Partial<Record<keyof User, boolean>>) {
    const updated: Partial<User> = {}

    Object.keys(dirtyFields).forEach(field => {
        updated[field] = v[field as keyof User]
    })

    return updated
}
    

However, an error occurs at updated[field]:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Partial<User>'.
  No index signature with a parameter of type 'string' was found on type 'Partial<User>'.(7053)

Answer №1

An issue arises where the field is considered a type of string, and the class User lacks a string index signature. This occurs because Object.keys(obj) returns string[] instead of Array<keyof typeof obj> due to reasons explained in response to Why doesn't Object.keys return a keyof type in TypeScript?.

If you desire the compiler to handle each element at keyof User, you must assert the type somewhere. My common approach is to use

(Object.keys(dirtyFields) as Array<keyof User>)
.

However, there will still be an error:

(Object.keys(dirtyFields) as Array<keyof User>).
    forEach(field => {
        updated[field] = v[field]; // error!
        //^^^^^^^^^^^^
        // Type 'string' is not assignable to type '"ADMIN" | "USER" | undefined'.
    });

This error stems from the fact that the compiler does not recognize that field is the same on both sides of the assignment. It interprets it as if updated[field1] = v[field2], where both field1 and field2 are of type keyof User. To address this issue, there is a workaround mentioned in a comment on the same issue: utilize generics:

(Object.keys(dirtyFields) as Array<keyof User>).
    forEach(<K extends keyof User>(field: K) => {
        updated[field] = v[field];  // works fine now
    })

In this case, the callback to forEach() becomes generic in K, which represents the type of field, constrained to keyof User. The right side of the assignment is recognized as User[K], while the left side is Partial<User>[K], resulting in a successful assignment.

Playground link to code

Answer №2

On Discord, a user named word#1438 provided me with a helpful solution


interface Person = {
    name: string
    email: string
    role: 'ADMIN' | 'USER'
}

function updateDetails(p: Person, changes: Partial<Record<keyof Person, boolean>>) {
    const updatedData: Partial<Record<keyof Person, string>> = {}

    function isPropertyOfPerson(key: string): key is keyof Person {
        return key in p
    }

    Object.keys(changes).forEach(ch => {
        if(!isPropertyOfPerson(ch)) return
        updatedData[ch] = p[ch as keyof Person]
    })

    return updatedData
}

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

Issue encountered when trying to view images stored locally

As a beginner in the world of Angular framework and web development, I am struggling to display images stored in local storage. <tr *ngFor = "let item of unused; let i = index ; "> <div style="padding-left:25%;padding-top:0%;" class="row" ...

Strategies for resolving the TypeScript 'possibly null' issue in ternary situations

Looking to enhance my code with a ternary operator for adding a class: className={clsx(classes.text, { classes.textSmall]: children.length > 11, })} Although this approach works, a TypeScript error is triggered: Object is possibly 'null' ...

Troubleshooting Angular 2: Why Array Interpolation is Failing

Greetings everyone, I am diving into Angular 2 and attempting to create a basic Todo application. Unfortunately, I've hit a roadblock. My array interpolation seems to be malfunctioning. Any assistance would be greatly appreciated. Here is my AppCompo ...

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 ...

AngularJS and CSS: A Guide to Effortlessly Toggle Sliding List Elements

I am in the process of developing a drop-down menu that can be clicked. Using my custom AngularJS directive, I have successfully implemented functionality to load menu items dynamically. While I have made significant progress, I have encountered a small i ...

How can I effectively pass an array of objects to an interface in React with Typescript?

In my code, I have defined two interfaces as shown below: export interface TableBoxPropsHeader{ name: string, isIconOnly: boolean } export interface TableBoxProps<T> { headerNames: TableBoxPropsHeader[], // some stuff } I am currently fa ...

Techniques for a versatile class limited to a particular category

In my code, I have a Vector class that looks like this: class Vector<N extends number> {...} N represents the size or dimension of the vector. This Vector class also includes a cross product method to calculate the cross product between vectors: cro ...

No GraphQL type definitions were discovered for the specified pointers: src/**/*.graphql

I am utilizing the @graphql-codegen/cli tool to automatically generate TypeScript types from my GraphQL server. Below is the content of my codegen.yml: overwrite: true schema: "http://localhost:3001/graphql" documents: "src/**/*.graphql" generates: src/ ...

Unlocking the union of elements within a diverse array of objects

I have an array of fields that contain various types of input to be displayed on the user interface. const fields = [ { id: 'textInput_1', fieldType: 'text', }, { id: 'selectInput_1', fieldType: 'sel ...

`How can TypeScript scripts be incorporated with Electron?`

As I work on my simple electron app, I am facing an issue where I need to include a script using the following code: <script src="build/script.js"></script> In my project, I have a script.ts file that compiles to the build folder. im ...

Navigating to a specific attribute within a higher-level Component

Within my top-level Component, I have a property that is populated with data from an HTTP source. Here is how it is implemented in a file named app.ts: import {UserData} from './services/user-data/UserData'; Component({ selector: 'app& ...

Changing an Angular template.html into a PDF document within an Angular 2 application can be achieved by utilizing

Exploring Angular 2 and looking for a way to export my HTML component in Angular 2 to PDF using jspdf. I want to convert dynamically generated tabular HTML into a PDF using jspdf. Below is a snippet of sample code along with a Plunker link: import {Comp ...

Angular 2 validation issue not functioning as anticipated

Here is a validator function that I have: export const PasswordsEqualValidator = (): ValidatorFn => { return (group: FormGroup): Observable<{[key: string]: boolean}> => { const passwordCtrl: FormControl = <FormControl>group.contr ...

Using Typescript to implement an onclick function in a React JS component

In my React JS application, I am using the following code: <button onClick={tes} type="button">click</button> This is the tes function that I'm utilizing: const tes = (id: string) => { console.log(id) } When hovering ov ...

What's Preventing TypeScript Enum Keys from Being Transformed during Compilation?

I've encountered an issue while working on a project with TypeScript, Webpack, Babel, and React. The problem arises when trying to use enum members as keys for an object. Here's a snippet of the problematic file: // traits.ts import { Trait } fr ...

How can I remove a row from a mat table using Angular?

Having trouble implementing *ngFor in my angular mat table, seeking guidance from someone with more expertise? I am trying to delete a row within an array using a button and display it on my table, but encountering issues. I intend to utilize *ngFor to sh ...

Converting a Typescript project into a Node package: A step-by-step guide

I'm currently dealing with an older typescript project that has numerous functions and interfaces spread out across multiple files. Other packages that depend on these exports are directly linked to the file in the directory. My goal is to transition ...

Determine whether an interface includes a mandatory field

Can Typescript's Conditional Types be used to determine if an interface includes a required field? type AllRequired = { a: string; b: string } type PartiallyRequired = { a: string; b?: string } type Optional = { a?: string; b?: string } // Can we mo ...

typescript error is not defined

While browsing online, I came across a post discussing how to transfer data from an MVC model to a .ts file. The suggestion was to include the following code: <script type="text/javascript"> var testUrl = @Html.Raw(Json.Encode(Model.testUrl) ...

Using the angular2-cookie library in an Angular 2 project built on the rc5 version

Starting a new angular2 rc5 project, I wanted to import the angular2 cookie module. After installing the module with npm, I made changes to my angular-cli-build.js file : npm install angular2-cookie edited my angular-cli-build.js file : module.exports ...