Home > Net >  How to exclude specific keys value from a typed object?
How to exclude specific keys value from a typed object?

Time:12-22

I Have the following check logic that checks from a certain type and a component that gets props there are 2 types of props mandatory and optional

in this current case the wight props is optional the issue is the isCurrentType function returns 2 types and the el.weight is erroring out since the other type has no wight on it I have this feeling that I am kind of over complicating the architecture.

const CurrentView: React.FC<Props> = () => {

cosnt items = useAppSelector((state) => state.data.itemsFromDB);

type TypeA = {id:string; equipment:string; weight:string}
type TypeB = {id:string; equipment:string;}

export const isCurrentType = (item: any): item is TypeA | TypeB => {
  return ['typeA', 'TypeB'].includes(item?.itemType);
};

const renderItems = items.map((el) => {
if (isCurrentType(el)) {
        return (
          <Component
            id={el.id}
            equipment={el.equipment}
            weight={el.weight/*Erorr on el.weight*/ ? el.weight : undefined}       
          />
        );
}})

return(<div>{renderItems}</div>)
}

CodePudding user response:

The compiler will not let you access properties on a union type unless every member of the union has a known property at that key:

type U = { id: string, weight: string } | { id: string }
declare const u: U;
u.id.toUpperCase(); // okay
u.weight // error!   Property 'weight' does not exist on type '{ id: string; }'.

This is because object types in TypeScript are not sealed or exact. It is technically possible for a value of type {id: string} to contain extra properties like weight (although excess property checking can complicate this):

const something = { id: "abc", weight: 123 }
const oops: U = something; // this is accepted

That compiles because someThing matches {id: "abc"}, and weight of type number doesn't invalidate that. You were assuming that a value of type U either had a string-valued weight property, or was missing the weight properly entirely. But the assignment const oops: U = something shows that this assumption is technically incorrect, and that's the problem you're running into.


There are a few ways around this. You could explicitly prohibit weight on the second union element, by making it an optional property whose value is the impossible never type:

type U = { id: string, weight: string } | { id: string, weight?: never } 
// now weight is known in every member of the union --> ^^^^^^^^^^^^^^

declare const u: U;
u.weight // okay, string | undefined

const something = { id: "abc", weight: 123 }
const oops: U = something; // error! 

That works now; the type of u.weight is known to be either string or undefined. And the compiler catches the assignment of something to oops because now {id: string, weight: number} is no longer assignable to U. That is, now it is correct to assume that weight is either present and string-valued, or absent (more or less).


A less intrusive fix is to use a narrowing technique that makes the same technically-incorrect assumption as you were making. The compiler will treat use of the in operator as a way to narrow a union-typed value to those union members known to have the checked property:

"weight" in u ? u.weight : undefined // okay, string | undefined

When the "weight" in u check is true, the compiler assumes that u.weight is of type string, so it lets you evaluate u.weight. This narrowing is technically unsafe, so this method is only appropriate if you can be relatively sure that the oops assignment will happen. After all, that still compiles without error:

const something = { id: "abc", weight: 123 }
const oops: U = something; // still no error 

But as long as your code doesn't do things like that, in-operator narrowing should work for you.

Playground link to code

  • Related