I have the next interfaces
interface ICustomImage {
data: string;
width: number;
height: number;
}
type UserImage = string | ICustomImage
interface IUser {
id: number;
firstName: string;
lastName: string;
image: UserImage
}
And I'm getting the IUser from an API and storing it in a useState like this:
const [user, setUser] = useState<IUser[]>();
But when I try to use the autocomplete this happens instead of showing all methods that a string have or ICustomImage properties
user.image.width //Because I know it comes with width, this error appears
Property 'width' does not exist on type 'string | ICustomImage'. Property 'width' does not exist on type 'string'.
How can I fix this, or make it so that I can use the following
user.image.data or user.image.width or user.image.height
CodePudding user response:
You have to narrow the type of user.image
before accessing its attributes.
If you don't you will only be allowed to access the intersected/shared attributes which are toString
and valueOf
.
You could do something like:
const someNumber = typeof user.image === 'string' ? user.image.length : user.image.height
CodePudding user response:
Typescript is complaining because user.image
can be of type string
, so you can't access the width
property. You'll need to use the in
Keyword to check for types in TypeScript.
if ("width" in user.image) {
const width = user.image.width; // should be ok
}
As you'll have to check multiple properties (height, data, etc) you can define a type guard function.
If you're absolutely sure it'll be of type ICustomImage
then you can cast the value as follow :
const image = user.image as ICustomImage
const width = image.width;
Regarding your state name, it should be plurals (because it's an array of user).
// Multiple users
const [users, setUsers] = useState<IUser[]>();
// Single user
const [user, setUser] = useState<IUser>();
CodePudding user response:
Typescript has no way to know whether the image
is a string or your interface object. You haven't narrowed it down for the interpreter and need to do so. Write a type guard to handle this:
interface CustomImage {
data: string
width: number
height: number
}
interface User {
id: number
firstName: string
lastName: string
image: CustomImage | string
}
const hasCustomImage = (object: any): object is CustomImage => {
return !!object.data
}
const userCustomImage: User = {
id: 1,
firstName: "first",
lastName: "last",
image: {
data: 'data',
width: 1024,
height: 768
}
}
const userStringImage: User = {
id: 2,
firstName: "first",
lastName: "last",
image: 'image'
}
console.log(`typeguard against custom image object: ${hasCustomImage(userCustomImage.image)}`)
console.log(`typeguard against string image: ${hasCustomImage(userStringImage.image)}`)
const dataImage = hasCustomImage(userCustomImage.image) && userCustomImage.image.data || userCustomImage.image
const stringImage = hasCustomImage(userStringImage.image) && userStringImage.image.data || userCustomImage.image
console.log(dataImage)
console.log(stringImage)
Check it out on the playground. You could also do an inverse check where your type guard checks to see if the object is a string but this should give you some direction.