skip to Main Content

I have the following type:

type FormData = {
   field1: string;
   field2: string;
   fieldₙ: string; 
}

I want to create a function for React that receives just one key, and one value, being the key one of those in the previous type (FormData).

So, in this case, I want to create a type that could encase the following:

{field1: string} or {field2: string} and all other potential fields.

Basically the idea is to being able to type a function like this:

(data: {field from FormData: string}): void

Is there any way to create a type like this?

I tried using [key in FormData]:string, but that forces me to have all fields, and not just one.

I don’t want to make them all ignorable with ?.

3

Answers


  1. You can use Typescript Mapped Types and declare a type like this

    type SingleField = {
        [K in keyof FormData]: string 
        // you can infer the type like that also FormData[K]
    }
    

    You can also use this implementation with generics and template literal types

    Edit

    Since you want at least one key, you can use this implementation:

    type SingleField = {
        [K in keyof FormData]: { [P in K]: string }
        // you can infer the type also with FormData[P] 
        & Partial<Record<Exclude<keyof FormData, K>, never>>;
    }[keyof FormData];
    
    Login or Signup to reply.
  2. Something like this? Notice it supports different value-types for the fields, and the resulted-object is also typed.

    type FormData = {
        field1: string;
        field2: number;
        field3: boolean;
    };
    
    function getSingleField<K extends keyof FormData>(
        key: K,
        formData: FormData
    ): { [P in K]: FormData[P] } {
        return { [key]: formData[key] } as { [P in K]: FormData[P] };
    }
    
    // Example usage
    const formData: FormData = {
        field1: "What's the answer?",
        field2: 42,
        field3: true,
    };
    
    const singleField = getSingleField("field2", formData);
    console.log(singleField); // { field2: 42 }
    

    Edit 1: Thinking about it, this problem could be abstracted to a generic type, and not be limited to FormData or a specified type:

    function getField<K extends keyof T, T>(
        key: K,
        data: T
    ): { [P in K]: T[P] } {
        return { [key]: data[key] } as { [P in K]: T[P] };
    }
    
    const exampleData = { a: "hello", b: 123 }
    const b = getField("b", exampleData);
    console.log(b); // { b: 123 }
    

    Edit 2: The logic can be further extended to any number of keys from any object type:

    export function getFields<K extends keyof T, T>(
        keys: K[] | K,
        data: T
    ): { [P in K]: T[P] } {
        if (!Array.isArray(keys)) { return getField(keys, data); }
        return keys.reduce((result, key) => {
            result[key] = data[key];
            return result;
        }, {} as { [P in K]: T[P] });
    }
    
    const exampleData = {
        a: "hello",
        b: 123,
        c: true,
        d: ["array", "of", "strings"],
    };
    
    const a_and_d = getFields(["a", "d"], exampleData);
    console.log(a_and_d); // { a: "hello", d: ["array", "of", "strings"] }
    const just_c = getFields("c", exampleData);
    console.log(just_c); // { c: true }
    
    Login or Signup to reply.
  3. You can use a self-indexing mapped type as follows:

    type Data = { field1: string; field2: string; fieldₙ: string; };
    
    type DataField = { [K in keyof Data]: { [SK in K]: Data[K] } }[keyof Data];
    

    Which gives you the following result type:

    type DataField = {
        field1: string;
    } | {
        field2: string;
    } | {
        fieldₙ: string;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search