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?
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;
};