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.

