"Implementing type definitions for a function that updates records with nested properties and callback support

I am currently working on a function that updates nested values within a record. I have different versions of this function for paths of varying depths.

My main struggle is figuring out how to properly type the callback function used when updating the value.

interface Test {
    foo?: { bar: number }
}
const input: Test = { foo: { bar: 1 } }

update(input, 'foo', 'bar')(v => v + 1)

Upon using the function, I encounter an error stating that "Object(v) is of type unknown".

Interestingly, I have another similar function called set which is defined similarly, but it receives proper typing when used like this:

set(input, 'foo', 'bar')(2)

Here's the update function implementation:

type UpdateFn<T> = (value: T) => T
export function update<T extends Record<string, any>, K1 extends keyof T>(
    record: T | undefined,
    key1: K1
): (callback: UpdateFn<NonNullable<T[K1]>>) => T
export function update<
    T extends Record<string, any>,
    K1 extends keyof T,
    K2 extends keyof NonNullable<T[K1]>
>(
    record: T | undefined,
    key1: K1,
    key2: K2
): (callback: UpdateFn<NonNullable<T[K1][K2]>>) => T

export function update<
    T extends Record<string, any>,
    K1 extends keyof T,
    K2 extends keyof NonNullable<T[K1]>
>(
    record: T | undefined,
    key1: K1,
    key2?: K2
): (
    callback:
        | UpdateFn<NonNullable<T[K1]>>
        | UpdateFn<NonNullable<T[K1][K2]>>
) => T | undefined {
    return callback => {
        if (record === undefined) return record

        if (key2 === undefined) {
            const value = get(record, key1)
            if (value === undefined) return record
            return set(record, key1)(callback(value))
        } else {
            const value = get(record, key1, key2)
            if (value === undefined) return record
            return set(record, key1, key2)(callback(value))
        }
    }
}

Set (working correctly):

export function set<
    T extends Record<string, any>,
    K1 extends keyof T,
    K2 extends keyof NonNullable<T[K1]>
>(record: T | undefined, key1: K1, key2: K2): (value: T[K1][K2]) => T

Answer №1

If I focus solely on addressing the typings instead of implementation, your second overload ought to look something like this:

export function modify<
    T extends Record<string, any>,
    K1 extends keyof T,
    K2 extends keyof NonNullable<T[K1]>
>(
    data: T | undefined,
    prop1: K1,
    prop2: K2
): (callback: ModifierFn<NonNullable<NonNullable<T[K1]>[K2]>>) => T

The inclusion of NonNullable ensures that you are referring to the type of data[prop1][prop2] in case both data and data[prop1] are defined and non-null. There might be alternative, more generic or neater ways to define the typings for modify(), but this rectifies the issue you are encountering:

modify(data, 'key', 'value')(fn => fn + 1); // working fine

Wishing you the best with this solution!

Click here for code examples

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

Why won't the sound play on the button with the picture?

I am currently working on a website project that requires buttons with pictures and sound. Despite my efforts, the sound feature is not functioning properly in Chrome and Firefox. I am still learning and would like to know how to toggle the sound on and of ...

Encountering the error "TypeError: null is not an object (evaluating '_ref.user')" with onAuthStateChanged in React Native using Firebase and useContext

I'm encountering an error in my useCachedResources.ts file and I'm uncertain of the cause. These three files are what I'm currently working with. I have a suspicion that the issue lies in the initial null value, but I am conditionally render ...

What is the best way to utilize await in promises instead of using then?

How can I correctly handle the Promise.all() method? I'm experiencing issues with resolving the promise that generates a series of asynchronous requests (simple database queries in supabase-pg SQL). After iterating through the results with a forEach l ...

Error encountered when providing valid data types as arguments in a React/Typescript function

I am facing an issue when passing a string variable to a function. To address this, I have created an interface called MyMessageProps where I declare the message as a string. Subsequently, the function MyMessage utilizes this interface to return with the ...

Connecting RxJS Observables with HTTP requests in Angular 2 using TypeScript

Currently on the journey of teaching myself Angular2 and TypeScript after enjoying 4 years of working with AngularJS 1.*. It's been challenging, but I know that breakthrough moment is just around the corner. In my practice app, I've created a ser ...

Setting default values on DTO in NestJS can be done by using the DefaultValue decorator provided

import { IsString, IsNumber, IsOptional, IsUUID, Min, Max } from 'class-validator'; import { Transform } from 'class-transformer'; export class QueryCollateralTypeDto { @Transform(({ value }) => parseInt(value)) @IsNumber() @I ...

Typescript versus ES5: A comparison of Node.js server-side applications written in different languages

Note: When I mention regular JavaScript, I am referring to the ES5 version of JS. As I lay down the groundwork for a new project, my chosen tech stack consists of Node.js for the back-end with Angular2 for the front-end/client-side, and Gulp as the build ...

Leveraging import and export functionality in TypeScript while utilizing RequireJS as a dependency

I am in the process of transitioning a complex JavaScript application from Backbone/Marionette to TypeScript. While making this shift, I want to explore the benefits of exporting and importing classes using files as modules. Is it necessary to incorporat ...

What steps can be taken to address the InvalidPipeArgument error when working with dates?

When attempting to format a date in a specific way using the pipe date, I encountered an error: Uncaught Error: InvalidPipeArgument: 'Unable to convert "25/01/2019" into a date' for pipe 'e' at Xe (main.fc4242d58c261cf678ad.js:1) ...

Testing the addition of a dynamic class to an HTML button using Jasmine unit tests

I am brand new to Jasmine and currently in the process of grasping how to write Unit tests for my components in Angular 4. One issue I encountered is when I attempt to add a class to the button's classList within the ngOnInit() lifecycle hook of the C ...

Patiently waiting for the component variable to be assigned through subscription

I am facing an issue with two calls in my component. The second call depends on the result from the first call. In the first call, I set the value for my component variable "locked". The second call should only be executed when the result is true, meaning ...

Modifying the value of a property in an object array created using the map method is ineffective

I have a collection of objects: https://i.sstatic.net/XNrcU.png Within the collection, I wished to include an additional property to the objects. To achieve this, I utilized the map function: returnArray = returnArray.map((obj) => { obj.active = "fal ...

Using TypeScript with React's forwardRef

Here's my code where I have utilized React's forwardRef: interface PropsDummy {} const ProfileMenu = forwardRef<HTMLInputElement, PropsDummy>((props, ref) => { console.log(ref.current); } However, I'm encountering a TypeScript e ...

What is the specific type of event for a change handler in TypeScript?

As a newcomer to TypeScript, I recently crafted a change handling function that accepts the event as a parameter to assign the value, like event.target.value. Currently, I have designated this as any, but I suspect there is a more appropriate type for this ...

Utilizing i18next for both a custom Typescript library and a host simultaneously: a step-by-step guide

Currently, I am in the process of developing a typescript library that is designed to take in an object and generate an excel file. This library is intended for use with multiple React applications. Each React application, or host, will provide its own obj ...

Experiencing unfamiliar typescript glitches while attempting to compile a turborepo initiative

I have been utilizing the turborepo-template for my project. Initially, everything was running smoothly until TypeScript suddenly started displaying peculiar errors. ../../packages/fork-me/src/client/star-me/star-me.tsx:19:4 nextjs-example:build: Type erro ...

Tips for patiently anticipating the outcome of asynchronous procedures?

I have the given code snippet: async function seedDb() { let users: Array<Users> = [ ... ]; applications.map(async (user) => await prisma.user.upsert( { create: user, update: {}, where: { id: user.id } })); } async function main() { aw ...

Are you ready to put Jest to the test by checking the completion event of

The RxJS library's Observer triggers three main events: complete error next If we want to verify the occurrence of the complete event using Jest, how can this be achieved? For instance, we are able to test the next and error events by checking for ...

Transformer Class: An object containing properties that are instances of another class

class ClassA { x: number; y: number; sum(): number { return this.x + this.y; } } class ClassB { @Type(() => ClassA) z: {[key: string]: ClassA}; } const b = transformObject(ClassB, obj); const z = b.z[key]; const s = z.s ...

How to use RxJs BehaviorSubject in an Angular Interceptor to receive incoming data

Being a newcomer to rxjs, I grasp most operators except for the specific use case involving BehaviorSubject, filter, and take. I am working on renewing an oauth access and refresh token pair within an Angular interceptor. While reviewing various codes fro ...