Last updated: Feb 29, 2024
Reading timeยท7 min
The error "Object is possibly 'null'" occurs when we try to access a property
on an object that may have a value of null
.
To solve the error, use the optional chaining operator to short-circuit if the
reference is equal to null
, e.g. emp?.address?.country
.
Here is an example of how the error occurs.
type Employee = { address: { country: string; city: string; } | null; // ๐๏ธ could be null }; const emp: Employee = { address: null, }; // โ๏ธ Error: Object is possibly 'null'.ts(2531) console.log(emp.address.country);
The address
property on the Employee
type could be null
which causes the
error.
We can use the optional chaining (?.) operator to get around this.
type Employee = { address: { country: string; city: string; } | null; }; const emp: Employee = { address: null, }; // โ No errors console.log(emp?.address?.country); // ๐๏ธ using optional chaining
The question mark dot (?.) syntax is called optional chaining in TypeScript.
It is like using dot notation to access a nested property of an object, but
instead of causing an error if the reference is nullish (null
or undefined
),
it short-circuits returning undefined
.
An alternative approach is to use a simple if
statement that serves as a
type guard.
type Employee = { address: { country: string; city: string; } | null; }; const emp: Employee = { address: null, }; // ๐๏ธ check if not null or undefined if (emp.address != null) { console.log(emp.address.country); console.log(emp.address.city); }
We used an if
statement to check if the emp.address
property is not equal to
null
or undefined
.
if
block, TypeScript knows that the country
and city
properties are of type string
.Notice that we used the loose inequality (!=) operator, which checks for both
null
and undefined
. You can exclusively
check for null with strict not equals (!==).
The loose comparison covers both null
and undefined
, because in a loose
comparison null
is equal to undefined
.
console.log(null == undefined); // ๐๏ธ true console.log(null === undefined); // ๐๏ธ false
You can also use the non-null assertion operator if you are sure the property
could not have a value of null
.
type Employee = { address: { country: string; city: string; } | null; }; const emp: Employee = { address: { country: 'Germany', city: 'Hamburg', }, }; console.log(emp.address!.country); // ๐๏ธ "Germany"
The exclamation mark is the non-null assertion operator in TypeScript.
null
and undefined
from a type without doing any explicit type-checking.When you use this approach, you basically tell TypeScript that this value will
never be null
or undefined
.
We used it right after the address
property, so we are telling TypeScript that
emp.address
will never have a value of null
or undefined
.
If you are making a comparison in an if
statement, use the logical AND (&&)
operator to make sure the property is of the correct type.
type Employee = { address: { country: string; city: string; num?: number; } | null; }; const emp: Employee = { address: null, }; if ( emp.address && typeof emp.address.num === 'number' && emp.address.num > 50 ) { console.log('success'); }
The logical AND (&&) operator makes sure the address
property is not null
,
that num
exists on the address
object and is a number
before comparing it
to the number 50
.
This is needed because if the reference is nullish (null
or undefined
), the
optional chaining operator (?.) will return undefined
and TypeScript doesn't
allow us to compare undefined
to a number
.
For example, this would fail:
type Employee = { address: { country: string; city: string; num?: number; } | null; }; const emp: Employee = { address: null, }; // โ๏ธ could be undefined, so not allowed if (emp?.address?.num > 50) { console.log('success'); }
The result might have a value of undefined
because that's the return value of
the optional chaining (?.) operator when it short-circuits. TypeScript won't
allow us to compare a possibly undefined
value to a number.
Another common way to avoid getting the error is to use the logical AND (&&) operator when accessing the property.
type Employee = { address: { country: string; city: string; } | null; }; const emp: Employee = { address: null, }; if (emp.address && emp.address.country) { // ๐๏ธ emp.address.country is type string here console.log(emp.address.country.toUpperCase()); }
null
).All of the values in the if
condition have to be truthy for the if
block to
run.
The falsy values in JavaScript are: undefined
, null
, false
, 0
, ""
(empty string), NaN
(not a number).
All other values are truthy.
emp.address.country
property to be string
in the if
block.typeof
operator to solve the errorAn even better way to get around the error in this situation is to use the typeof operator.
type Employee = { address: { country: string; city: string; } | null; }; const emp: Employee = { address: null, }; if (emp.address && typeof emp.address.country === 'string') { // ๐๏ธ emp.address.country is type string here console.log(emp.address.country.toUpperCase()); }
We explicitly check if the type of the country
property is a string. This is
better than checking if the value is truthy
because empty strings are falsy
values in JavaScript (and TypeScript).
Here is an example that illustrates why using typeof
is better.
type Employee = { address: { country: string; city: string; } | null; }; const emp: Employee = { address: { country: '', city: '', }, }; if (emp.address && emp.address.country) { const result = emp.address.country; console.log(result); } else { // ๐๏ธ else block runs console.log('โ This block runs'); }
The else
block runs in the example.
country
property points to an empty string (falsy value), so just checking if the value is truthy might not be enough in your scenario.It's always better to be explicit and use the typeof
operator when possible.
This helps us avoid some difficult-to-spot bugs.
The error with document.getElementById
occurs because the method returns
null
if no element with the provided id
is found.
To solve the error, use a non-null assertion or a type guard to verify the
variable doesn't store a null
value.
This is the index.html
file for the examples.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> </head> <body> <button id="btn">Click</button> <script src="./src/index.ts"></script> </body> </html>
And here are 2 examples of how the error occurs.
// ๐๏ธ const button: HTMLElement | null const button = document.getElementById('btn'); // โ๏ธ Object is possibly 'null'.ts(2531) button.addEventListener('click', () => { console.log('button clicked'); }); // โ๏ธ Object is possibly 'null'.ts(2531) console.log(button.innerText);
The document.getElementById method
returns null
if no element with the provided id
is found in the DOM.
The button
variable has a type of HTMLElement | null
, therefore we can't
directly access a property on it, because if we access a property on a null
value, we'd get a runtime error.
if
statement that serves as a type guardThe best way to solve the error is to use a simple if
statement that serves as
a type guard.
// ๐๏ธ const button: HTMLElement | null const button = document.getElementById('btn'); // ๐๏ธ button has type HTMLElement or null here if (button != null) { // ๐๏ธ button has type HTMLElement here button.addEventListener('click', () => { console.log('button clicked'); }); console.log(button.innerText); }
We make sure that the button
variable does not store a null
value before
accessing properties on it.
if
block, TypeScript knows that the type of the button
variable is HTMLElement
and it couldn't be null
.An alternative approach to solve the error is to use the optional chaining (?.) operator.
// ๐๏ธ const button: HTMLElement | null const button = document.getElementById('btn'); // ๐๏ธ using optional (?.) chaining button?.addEventListener('click', () => { console.log('button clicked'); }); // ๐๏ธ using optional chaining (?.) console.log(button?.innerText);
The optional chaining (?.) operator short-circuits returning undefined
if the
reference is nullish (null
or undefined
).
button
variable stores a null
value, we won't attempt to access the addEventListener
method and get a runtime error.We could have also used the non-null assertion (!) operator.
// ๐๏ธ const button: HTMLElement | null const button = document.getElementById('btn'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion button!.addEventListener('click', () => { // ๐๏ธ non-null assertion console.log('button clicked'); }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion console.log(button!.innerText); // ๐๏ธ non-null assertion
The exclamation mark is the non-null assertion operator in TypeScript.
null
and undefined
from a type without doing any explicit type-checking.When you use this approach, you basically tell TypeScript that this value will
never be null
or undefined
.
This is very similar to a type assertion and should only be used when you're absolutely sure that the value is of the expected type.
const button = document.getElementById('btn') as HTMLElement; button.addEventListener('click', () => { console.log('button clicked'); }); console.log(button.innerText);
Type assertions are used when we have information about the type of a value that TypeScript can't know about.
button
will be an HTMLElement
and not to worry about it.We could have also used a more specific type - HTMLButtonElement
because we're
working with a button element.
const button = document.getElementById('btn') as HTMLButtonElement; button.addEventListener('click', () => { console.log('button clicked'); }); console.log(button.innerText);
Using a more specific type enables us to access button-specific properties that
don't exist in the HTMLElement
interface.
HTML***Element
. Once you start typing HTML..
, your IDE should be able to help you with autocomplete.Some commonly used types are: HTMLInputElement
, HTMLButtonElement
,
HTMLAnchorElement
, HTMLImageElement
, HTMLTextAreaElement
, etc.
You can learn more about the related topics by checking out the following tutorials: