Key constraints in generics: a key must belong to a distinct object type

Is there a way to enhance the safety of this function even further?

Consider this object/shape:

export const initialState: State = {
    foods: {
        filter: '',
        someForm: {
            name: '',
            age: 2,
        },
        someFormWithoutAnAge: {
            name: '',
        },
    }
};

declare function pickAForm<R extends keyof State, S extends keyof State[R]>(key1: R, key2: S): void;

The current function pickAForm ensures type safety with examples like pickAForm("foods", "someForm") working as expected and catching errors like

pickAForm("foods", "somePropertyThatDoesntExist")

Now, I want to enforce an additional safety measure where selections must have a specific shape. For instance, selecting someForm should be valid, whereas choosing someFormWithoutAnAge should result in failure because it lacks an age property. This can be achieved by introducing:

declare function pickAFormWithAge<R extends keyof State, S extends keyof State[R], T extends State[R][S] & {age: number}>(key1: R, key2: S): void;

The challenge now is implementing this enhancement. To clarify:

pickAFormWithAge('foods', 'someForm') // Should pass
pickAFormWithAge('foods', 'someFormWithoutAge') // Should fail, as it doesn't match the required shape {age: number}
pickAFormWithAge('foods', 'blah') // Should fail because 'blah' is not a valid key

Answer №1

To achieve this, I made the following adjustments:

a. Ensuring that the data structure aligns with the string literals, rather than vice versa.
b. Passing the data structure as a function parameter.

const state = {
    foods: {
        filter: '',
        someForm: {
            name: 'Some form',
            age: 2
        },
        someFormWithoutAnAge: {
            name: 'other form',
            priority: 10
        }
    }
};

interface HasAge { age: number }

// IMPLEMENTATION
function getForm<O extends {[P in P1]: {[P in P2]: HasAge}}, P1 extends string, P2 extends string>(o: O, p1: P1, p2: P2) {
    return (o[p1] as any)[p2] as any;
}

// USAGE
const form1 = getForm(state, 'foods', 'someForm'); // SUCCESSFUL
const form2 = getForm(state, 'foods', 'someFormWithoutAnAge'); // ERROR
const form3 = getForm(state, 'foods', 'blah'); // ERROR

A more straightforward and adaptable approach involves using regular code. The function pickAForm accepts a function instead of direct string values.

const form1 = pickAForm((state) => state.foods.someForm);
// ...or...
const form1 = pickAForm(() => initialState.foods.someForm);

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

React encountered an issue: each child element within a list must be assigned a unique "key" prop

I am feeling a bit puzzled as to why I keep getting the error message: child in a list should have a unique "key" prop. In my SearchFilterCategory component, I have made sure to add a key for each list item using a unique id. Can you help me figu ...

What is the syntax for creating a function with parameters of type `any` or `void` in TypeScript?

How can I create a function in typescript that accepts either something or nothing as input? I attempted the following: interface TestFn { (input: any | void): string } const operation: TestFn = (input) => 'result'; operation('some ...

Ion-List seamlessly integrates with both ion-tabs and ion-nav components, creating a cohesive and dynamic user interface

On my homepage, there is an ion-list. Sometimes (not every time), when I select an item in this list or navigate to the register page using "this.app.getRootNav().push("ClienteCadastroPage")", and then select an input in the registerPage or descriptionPage ...

Is it possible that Angular 6's ngOnChanges directive is not compatible with a lambda expression?

Currently, I am in the process of creating an attribute directive that is designed to alter the background color of the host element based on a provided "quality" @input. While experimenting with my directive, I discovered that using ngOnChanges as a lamb ...

What is the best way to insert an item into a tree structure when determining the appropriate level for insertion is necessary beforehand?

Currently, I am dealing with a tree-like object structure: interface Node { id: number; name: string; children?: Node[]; } NODE_DATA: Node[] = [ { id: 1, name: 'A' }, { id: 2, name: 'B', children: [ ...

The recommended filename in Playwright within a Docker environment is incorrectly configured and automatically defaults to "download."

Trying to use Playwright to download a file and set the filename using download.suggestedFilename(). Code snippet: const downloadPromise = page.waitForEvent('download', {timeout:100000}) await page.keyboard.down('Shift') await p ...

Dynamically incorporating validation to an ngModel input

Utilizing NgForm, I dynamically added a validator to the input field. In my first example, everything works perfectly when I use the button setValidation to validate the input. However, in the second example where I add formGroup, I encounter an error whe ...

Trouble with Typescript in VSCode made easy

Setting up a VSCode environment for working with TypeScript v2.03 has been challenging. Beginning with a simple vanilla javascript snippet that can be tested in node via the integrated terminal. function Person() { this.name = ""; } Person.prototy ...

Using template literals with Optional chaining in Javascript does not yield the expected results

Trying to implement template literal with optional chaining. type Item = { itemId:number, price: number}; type ItemType = { A:Item, B:Item }; const data : ItemType = { A:{itemId:1, price:2}, B:{itemId:2, price:3} }; let key = `data?.${variable}?.ite ...

Embedding a TypeScript React component within another one

Currently, I'm facing an issue with nesting a TypeScript React component within another one, as it's causing type errors. The problem seems to be that all props need to be added to the parent interface? Is there a way to handle this situation wi ...

What is the method for passing an element in Angular2 Typescript binding?

Is there a way to retrieve the specific HTML dom element passed through a binding in Angular? I'm having trouble figuring it out, so here is the relevant code snippet: donut-chart.html <div class="donut-chart" (donut)="$element"> ...

I'm curious if it's possible to set up both Tailwind CSS and TypeScript in Next.js during the initialization process

When using the command npx create-next-app -e with-tailwindcss my-project, it appears that only Tailwind is configured. npx create-next-app -ts If you use the above command, only TypeScript will be configured. However, running npx create-next-app -e with ...

Tips for inserting an object into an array

Here's the data I received: { 0:{modifierId: 4, modifierName: 'Garlic', modifierPrice: 60 } 1:{modifierId: 1, modifierName: 'Tartar ', modifierPrice: 60} 2:{modifierId: 3, modifierName: 'Herb ', modifierPrice: 60} item ...

The template literal expression is invalid due to the "string | null" type when sending authorization

While working on implementing authorization, I encountered an error from Ts-eslint stating that there was an "Invalid type 'string | null' of template literal expression" when trying to execute the functionality. The data being retrieved from lo ...

Collection of personalized forms where the parent is a FormGroup

One scenario I'm working on involves creating multiple custom formgroup classes that have FormGroup as their parent class, structured like this: export class CustomFormGroup1 extends FormGroup { //custom properties for this FormGroup const ...

My goal is to prevent users from using the Backspace key within the input field

Let's say we want to prevent users from using the backspace key on an input field in this scenario. In our template, we pass the $event like so: <input (input)="onInput($event)"> Meanwhile, in our app.component.ts file, the function ...

Problems with the zoom functionality for images on canvas within Angular

Encountering a challenge with zooming in and out of an image displayed on canvas. The goal is to enable users to draw rectangles on the image, which is currently functioning well. However, implementing zoom functionality has presented the following issue: ...

The selected image should change its border color, while clicking on another image within the same div should deselect the previous image

I could really use some assistance! I've been working on Angular8 and I came across an image that shows how all the div elements are being selected when clicking on an image. Instead of just removing the border effect from the previous image, it only ...

Encountering unusual results while utilizing interfaces with overloaded arguments

I came across a situation where TypeScript allows calling a method with the wrong type of argument. Why doesn't the TypeScript compiler flag this as an issue? interface IValue { add(value: IValue): IValue; } class NumberValue implements IValue { ...

Discovering new bugs in VSCode Playwright Tests but failing to see any progress

This morning, everything was running smoothly with debugging tests. However, after a forced reboot, I encountered an issue where it seems like the debugger is running, but nothing actually happens. This has happened before, but usually resolves itself. Unf ...