- Published on
TypeScript Error: Property `attribute` does not exist on type `{}`.
Understanding TypeScript Error TS2339
If you've been working with TypeScript for a while, you may have come across an error message that looks something like this:
Error: Property 'attribute' does not exist on type '{}'
This error message is known as TypeScript Error TS2339, and it's one of the most common errors you'll encounter when working with TypeScript.
What Causes TypeScript Error TS2339?
The error message indicates that you're trying to access a property called attribute
on an object, but TypeScript doesn't recognize that property as part of the object's type. In other words, the type of the object you're working with doesn't have a property called attribute
.
This error can occur for several reasons, but it's typically caused by a mismatch between the type of the object you're working with and the expected type. For example, you may have defined an interface
or type
that expects an object with an attribute
property, but the actual object you're working with doesn't have that property.
How to Fix TypeScript Error TS2339
To fix TypeScript Error TS2339
, you need to ensure that the object you're working with has the expected properties. There are a few steps you can take to resolve this error:
1. Check the Type of the Object
The first step is to check the type of the object you're working with. Make sure that the object's type includes the attribute property. If you're using an interface or type definition, verify that the definition matches the actual object you're working with.
For example, let's say you have an interface called Member
that defines an object with a fullName
property and a subscriptionID
property:
interface Member {
fullName: string
subscriptionID?: number
}
const member: Member = {
fullName: 'Johnny Smith',
};
const attribute = member.attribute //🚫 Error TS2339: Property 'attribute' does not exist on type 'Member'.
}
Solution To fix this error, you need to ensure that the object you are working with matches the expected type. In this case, you could add the missing property to the object:
interface Member {
fullName: string
subscriptionID?: number
attribute?: string
}
const member: Member = {
fullName: 'Johnny Smith',
};
const attribute = member.attribute; //No Error TS2339:
}
2. Dynamically add Property: Use Object's Index Signature
If you are working with an object that may or may not have the attribute
property, you can use Object - Index Signature to avoid TypeScript Error TS2339.
Here, there is no error on member.attribute
because the Member interface has an object index signature
which allows any string property keys to be used with either a string or number value.
interface Member {
fullName: string
subscriptionID: number
[key: string]: string | number // object index signature
}
const member: Member = {
fullName: 'John Smith',
subscriptionID: 12345,
}
const attribute = member.attribute // No error
if (typeof attribute === 'string') {
const lowerCaseAttribute = attribute.toLowerCase()
console.log(lowerCaseAttribute)
} else {
console.log(`Attribute is not a string: ${attribute}`)
}
This means that any additional properties on an object of type Member can have a key of type string, and a value of type string
or number
. So, when we define the member object, it doesn't have an attribute
property, but it's still allowed by the interface due to the object index signature.
When we assign member.attribute
to the attribute variable, TypeScript does not throw an error because it assumes that the attribute property exists on the member object due to the object index signature.
However, we still need to perform a type check with typeof attribute === 'string'
before calling the toLowerCase()
method because the attribute property can have a value of type number
, which does not have a toLowerCase()
method.
3. Dynamically add Property: Use Record<Keys, Type>
You can use TypeScript Utility Type Record instead of an interface Member
to inject when it's needed the attribute.
const member: Record<string, any> = {}
member.fullName = 'John Smith'
member.subscriptionID = 12345
const attribute = member.attribute // No error
console.log(member) //{fullName: "John Smith", subscriptionID: 12345}
console.log('attribute:', attribute) //attribute: undefined
Above by using Record
a general Typed object had being instructed and that was the reason TS2339 was avoided. In general this is not always preferred, but it can be used in slightly better manner as below by keeping the interface Member
and adding the ability to add Dynamically an attribute if needed.
interface Member extends Record<string, any> {
fullName: string
subscriptionID?: number
}
const member: Member = {
fullName: 'John Smith',
subscriptionID: 12345,
}
const attribute = member.attribute // No error
console.log(member) //{fullName: "John Smith", subscriptionID: 12345}
console.log('attribute:', attribute) //attribute: undefined
4. Dynamically add Property: Use Object.assign()
You can also use the Javascript's build-in Objects' static method Object.assign() to add any attribute at a later point; that was not part of your interface. That method copies all enumerable own properties from the source object dynamicAttribute
to the target object member
and it returns the modified object.
interface Member {
fullName: string
subscriptionID?: number
}
let member: Member = {
fullName: 'John Smith',
subscriptionID: 12345,
}
const dynamicAttribute = { attribute: undefined }
member = Object.assign(member, dynamicAttribute) // No error
console.log(member) // Returns {fullName: "John Smith", subscriptionID: 12345, attribute: undefined}
Note: For Deep Copy, you need to use alternatives, because Object.assign()
copies property values.