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:

ts-2339-1.tsx
 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:

ts-2339-1.tsx
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.

ts-2339-2.tsx
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.

ts-2339-3.tsx
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.

ts-2339-3.tsx
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.

ts-2339-4.tsx
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.