I have an item of state that can either have it’s own value, or reference another item via an id.
interface Item {
name: string;
value: { valueLiteral: number } | { id: string };
}
const item1: Item = {
name: "foo1",
value: {
valueLiteral: 1
}
};
const item2: Item = {
name: "foo2",
value: {
id: "some-id"
}
};
I need to have an array of these items and pass them to a React component, and have the component render differently depending on whether value
has a valueLiteral
or id
property.
This code works but gets a TypeScript error:
https://codesandbox.io/s/strange-wilbur-i4bvjm?file=/src/App.tsx
interface Item {
name: string;
value: { valueLiteral: number } | { id: string };
}
const item1: Item = {
name: "foo1",
value: {
valueLiteral: 1
}
};
const item2: Item = {
name: "foo2",
value: {
id: "some-id"
}
};
interface State {
items: Item[];
}
const state: State = {
items: [item1, item2]
};
function Item({ item }: { item: Item }) {
if (item.value.id) {
return (
<div>
<div>
{item.name} - ID = {item.value.id}
</div>
</div>
);
}
return (
<div>
<div>
{item.name} - VALUE = {item.value.valueLiteral}
</div>
</div>
);
}
export default function App() {
return (
<div className="App">
{state.items.map((item) => {
return <Item key={item.name} item={item} />;
})}
</div>
);
}
Property ‘id’ does not exist on type ‘{ valueLiteral: number; } | { id: string; }’.
Property ‘id’ does not exist on type ‘{ valueLiteral: number; }’.ts(2339)
How can I fix this error given my data modelling requirements?
I could have separate ID and value fields and make them both optional. This avoids the TypeScript errors but is less type safe as TypeScript doesn’t prevent Item
from having both or neither value
and id
, but this is not valid for my data modelling.
https://codesandbox.io/s/fervent-euler-h65899?file=/src/App.tsx
interface Item {
name: string;
value?: number;
id?: string;
}
const item1: Item = {
name: "foo1",
value: 1
};
const item2: Item = {
name: "foo2",
id: "some-id"
};
// item3 is invalid
const item3: Item = {
name: "foo3",
};
// item4 is invalid
const item4: Item = {
name: "foo3",
id: 'some-other-id',
value: 2
};
interface State {
items: Item[];
}
const state: State = {
items: [item1, item2]
};
function Item({ item }: { item: Item }) {
if (item.id) {
return (
<div>
<div>
{item.name} - ID = {item.id}
</div>
</div>
);
}
return (
<div>
<div>
{item.name} - VALUE = {item.value}
</div>
</div>
);
}
export default function App() {
return (
<div className="App">
{state.items.map((item) => {
return <Item key={item.name} item={item} />;
})}
</div>
);
}
3
Answers
Create a type or interface for the
value
:And then check the type:
To fix this error while still maintaining the type safety, you can use type guards. I believe this would fix your problem:
if (item.value.id) {...}
means ifitem.value.id
is truthy, but{ valueLiteral: number }
has noid
so how can I check that? That’s what TS complains about.TS doesn’t get what you’re implying with that check.
But you can use the in operator for that.
if ("id" in item.value) {...}
means ifitem.value
is an object that has anid
property. Here TS can understand that you’re differentiating between an object with and one without anid
property.