skip to Main Content

The useHider function was used to hide some values from an object with corret type, like if I use const res = useHider({ id: 1, title: "hi"}, "id"), it will come back with { title: "hi" } only, and when I try to use res.id, it will come with error in TypeScript.

The hideQueryResult function allow me to hide createdAt, updatedAt and deletedAt by default, or I can add some params to hide more values from an object.

const useHider = <T, K extends keyof T>(obj: T, keysToHide: K[]) => {
    let res = obj;

    keysToHide.forEach((key) => {
        delete res[key];
    });

    return res as Omit<T, K>;
};

const hideQueryResult = <T, K extends keyof T>(
    query: T,
    keysToHide: K[] = [],
) => {
    const at = ["createdAt", "updatedAt", "deletedAt"] as K[];
    const allKeysToHide = [...keysToHide, ...at];
    const res = useHider(query, allKeysToHide);
    return res;
};

But when I try to use hideQueryResult to hide some values, it do not work as I expected.

const source = {
    id: "01ABC",
    title: "123",
    createdAt: "ABC",
    updatedAt: "ABC",
    deletedAt: "ABC",
};

const res1 = useHider(source, ["createdAt", "updatedAt", "deletedAt"]);

console.log(res1.id); // success (expected)
console.log(res1.createdAt); // failed (expected)

const res2 = hideQueryResult(source);

console.log(res2.id); // failed (not expected)

const res3 = hideQueryResult(source, ["id"]);

console.log(res3.createdAt); // success (not expected)

What can I do to make it work?

2

Answers


  1. you should first make a shallow copy from the obj. because if you don’t, the res will be reference to obj. so, if you try to delete a property from res, it will actually delete from obj. but with this syntax ({...obj}) you can make a shallow copy and delete/add the properties you want.

    const useHider = <T, K extends keyof T>(obj: T, keysToHide: K[]) => {
        let res = {...obj}; // edited line
    
        keysToHide.forEach((key) => {
            delete res[key];
        });
    
        return res as Omit<T, K>;
    };
    

    note: shallow copy will only copy the first keys and values, the values may still refer to the main values. so:

    const obj = { 
        name: "John", 
        data: {
            age: 10
        }
    }
    
    const clone = {...obj}
    
    clone.username = "Alex"
    console.log(obj.username) // John
    
    clone.data.age = 20
    console.log(obj.data.age) // 20
    
    clone.data = null
    console.log(obj.data) // { age: 20 }
    

    Now, to solve the type issues. you need to tell the typescript that the object that goes to the useHider through hideQueryResult can have those 3 fields. and then give it a default value, so when we don’t give the 2nd argument, typescript will not infer every single key automatically and instead use the never type as the default value which means there is not any set data.

    const fieldsToRemove = ["createdAt", "updatedAt", "deletedAt"] as const // `const` makes it readonly and type-friendly
    
    const hideQueryResult = <T, K extends keyof T = never>( // `never` means no data is set
        query: T,
        keysToHide: K[] = [],
    ) => {
        const res = useHider(
            query as T & Record<typeof fieldsToRemove[number], unknown>, // Record makes an object type. (first arg is the keys and 2nd arg is the value type)
            [...fieldsToRemove, ...keysToHide]
        );
        return res;
    };
    
    Login or Signup to reply.
  2. First off, your use Hider function is modifying the original object because objects in JavaScript are passed by reference. So when you’re doing delete res[key];, you’re actually deleting the property from the original object. That’s a no-no for our tea party!

    Let’s make a shallow copy of the object first

    const useHider = <T, K extends keyof T>(obj: T, keysToHide: K[]) => {
    let res = { ...obj }; // Spread the object into a new one
    
    keysToHide.forEach((key) => {
        delete res[key];
    });
    
    return res as Omit<T, K>;
    };
    

    Now, for the hideQueryResult function, the issue is with the spread of keysToHide and at. When you spread them into allKeysToHide, TypeScript loses track of the exact keys you’re omitting. Instead, let’s concatenate them:

    const hideQueryResult = <T, K extends keyof T>(
    query: T,
    keysToHide: K[] = [],
    ) => {
    const at: K[] = ["createdAt", "updatedAt", "deletedAt"] as any;
    const allKeysToHide = keysToHide.concat(at);
    const res = useHider(query, allKeysToHide);
    return res;
    };
    

    maybe that will help?

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search