Cara Mengatasi Error ‘Object is possibly ‘null” di TypeScript
Pendahuluan
Jika kamu sudah pernah menulis kode TypeScript, kemungkinan besar kamu pernah melihat pesan error seperti ini:
Object is possibly 'null'.
Object is possibly 'undefined'.
Error ini bisa terasa menjengkelkan, terutama ketika kamu baru beralih dari JavaScript ke TypeScript. Tapi sebenarnya, TypeScript sedang membantu kamu — ia mencegah bug yang sangat umum di JavaScript: mengakses properti atau memanggil method pada nilai null atau undefined.
Di artikel ini, kita akan memahami mengapa error ini muncul, dan yang lebih penting, bagaimana cara mengatasinya dengan benar di berbagai situasi nyata, mulai dari pengecekan if, optional chaining ?., nullish coalescing ??, hingga non-null assertion !.
Memahami Penyebab Error: strictNullChecks
Error “Object is possibly ‘null’” muncul karena TypeScript memiliki fitur yang disebut strictNullChecks. Ketika fitur ini aktif (dan di mode strict, ini otomatis aktif), TypeScript membedakan antara tipe yang bisa bernilai null/undefined dan yang tidak.
Perhatikan contoh berikut:
// Aktifkan "strictNullChecks": true di tsconfig.json untuk contoh ini.
let nama: string | null = null; // Nilai boleh string atau null
if (nama === null) {
console.log("Nama belum diisi"); // Aman: TypeScript tahu nama masih null di blok ini
}
nama = "Budi"; // Sekarang nama berisi string
console.log(nama.toUpperCase()); // Aman: nama sudah pasti string setelah diberi nilai
/*
Output yang diharapkan:
> Nama belum diisi
> BUDI
*/
Artinya, jika sebuah variabel bertipe string, TypeScript memastikan nilainya pasti string — bukan null atau undefined. Untuk mengizinkan nilai null, kamu harus eksplisit:
let nama: string | null = null; // Variabel bisa berisi string atau null
let umur: number | undefined = undefined; // Variabel bisa berisi number atau undefined
nama = "Budi"; // Nilai null diganti dengan string
umur = 25; // Nilai undefined diganti dengan number
console.log(`Nama: ${nama}`);
console.log(`Umur: ${umur}`);
/*
Output yang diharapkan:
> Nama: Budi
> Umur: 25
*/
Mengapa error muncul? Karena TypeScript melihat bahwa variabelmu bisa bernilai null, tapi kamu langsung menggunakannya tanpa pengecekan:
function getElement(id: string): HTMLElement | null {
return document.getElementById(id); // Hasilnya bisa null
}
// ❌ Error: Object is possibly 'null'
// const tombol = getElement("submit-btn");
// tombol.addEventListener("click", handleClick); // TypeScript memblokir baris ini!
// ✅ Cara aman: periksa dulu
const tombol = getElement("submit-btn");
if (tombol !== null) {
console.log("Elemen ditemukan:", tombol.tagName);
} else {
console.log("Elemen tidak ditemukan");
}
Ini mirip dengan exception handling di Java — bahasa tersebut memaksa kamu menangani checked exceptions secara eksplisit. TypeScript melakukan hal serupa untuk nilai null.
Cara Aman Menangani Nilai null dan undefined
Ada beberapa teknik aman yang bisa kamu gunakan, tergantung konteksnya:
1. Pengecekan if (Type Guard)
Cara paling straightforward: cek dulu sebelum digunakan. Teknik ini disebut type guard.
// Membuat tombol agar contoh ini bisa langsung dijalankan di browser
const tombolBaru = document.createElement("button");
tombolBaru.id = "submit-btn";
tombolBaru.textContent = "Kirim";
document.body.appendChild(tombolBaru);
// Mengambil elemen tombol dari DOM berdasarkan id
const tombolSubmit = document.getElementById("submit-btn");
// TypeScript mempersempit tipe: di dalam blok ini, tombolSubmit pasti HTMLElement
if (tombolSubmit !== null) {
tombolSubmit.addEventListener("click", () => {
console.log("Tombol diklik!");
});
// Memicu klik secara otomatis agar output bisa terlihat saat kode dijalankan
tombolSubmit.click();
}
/*
Output yang diharapkan:
> Tombol diklik!
*/
TypeScript cukup pintar untuk menyempitkan tipe di dalam blok if — ini disebut type narrowing. Setelah pengecekan, TypeScript tahu bahwa tombolSubmit pasti bukan null.
Kamu juga bisa menggunakan pola typeof atau instanceof sebagai type guard:
function cetakPanjang(nilai: string | null): void {
if (typeof nilai === "string") {
// Di blok ini, TypeScript tahu nilai pasti string
console.log(`Panjang: ${nilai.length}`);
} else {
console.log("Nilai kosong, tidak bisa dihitung panjangnya");
}
}
cetakPanjang("TypeScript");
cetakPanjang(null);
/*
Output yang diharapkan:
> Panjang: 10
> Nilai kosong, tidak bisa dihitung panjangnya
*/
2. Optional Chaining (?.)
Kalau kamu tidak perlu melakukan sesuatu jika nilainya null, gunakan optional chaining. Operator ?. mengembalikan undefined alih-alih melempar error jika nilai di kirinya adalah null atau undefined.
interface User {
nama: string;
alamat?: {
kota: string;
provinsi: string;
};
}
// Data user tanpa properti alamat
const user: User = { nama: "Budi" };
// Optional chaining (?.) mencegah error saat alamat bernilai undefined
const kota = user.alamat?.kota;
console.log(`Nama: ${user.nama}`);
console.log(`Kota: ${kota}`); // Menampilkan undefined, tidak crash
/*
Output yang diharapkan:
> Nama: Budi
> Kota: undefined
*/
Optional chaining juga bisa dirantai untuk mengakses properti bersarang:
type User = {
nama: string;
alamat?: {
kota?: string;
};
};
const userDenganKota: User = {
nama: "Budi",
alamat: { kota: "Bandung" },
};
const userTanpaAlamat: User = {
nama: "Siti",
};
// Optional chaining (?.) mencegah error jika alamat atau kota bernilai undefined
const panjangKota1 = userDenganKota.alamat?.kota?.length;
const panjangKota2 = userTanpaAlamat.alamat?.kota?.length;
console.log(panjangKota1); // 7 (panjang "Bandung")
console.log(panjangKota2); // undefined, bukan error
/*
Output yang diharapkan:
> 7
> undefined
*/
3. Nullish Coalescing (??)
Gunakan ?? untuk memberikan nilai default ketika hasilnya null atau undefined. Berbeda dengan ||, operator ?? tidak mengganti nilai falsy seperti 0 atau "".
type User = {
nama: string;
alamat?: {
kota?: string | null;
};
};
const user: User = {
nama: "Dina",
// alamat sengaja tidak diisi
};
// Mengambil kota jika ada; jika alamat/kota null atau undefined, gunakan "Jakarta"
const kota = user.alamat?.kota ?? "Jakarta";
console.log(kota);
// ?? hanya mengganti nilai null/undefined, BUKAN nilai falsy seperti 0
const angka = 0 ?? 42;
console.log(angka); // 0, karena 0 bukan null/undefined
// Berbeda dengan ||, karena 0 dianggap falsy dan diganti dengan 42
const angka2 = 0 || 42;
console.log(angka2); // 42
/*
Output yang diharapkan:
> Jakarta
> 0
> 42
*/
4. Early Return (Guard Clause)
Pola ini sangat umum di kode TypeScript yang bersih — hentikan fungsi lebih awal jika nilai tidak valid:
interface User {
nama: string;
}
function prosesUser(user: User | null): void {
// Guard clause: hentikan fungsi jika user bernilai null
if (user === null) {
console.log("User tidak ditemukan");
return;
}
// Setelah pengecekan null, TypeScript tahu user pasti bertipe User
console.log(`Halo, ${user.nama}!`);
}
const userAktif: User = { nama: "Budi" };
prosesUser(userAktif);
prosesUser(null);
/*
Output yang diharapkan:
> Halo, Budi!
> User tidak ditemukan
*/
5. Type Assertion dengan as (Hati-hati!)
Kalau kamu yakin nilainya tidak akan null berdasarkan logika bisnis, kamu bisa menggunakan type assertion:
// Membuat form login agar contoh ini bisa langsung dijalankan di browser
const form = document.createElement("form");
form.id = "login-form";
const emailInput = document.createElement("input");
emailInput.name = "email";
emailInput.value = "[email protected]";
form.appendChild(emailInput);
document.body.appendChild(form);
// Type assertion: kita tahu elemen dengan id "login-form" adalah HTMLFormElement
const loginForm = document.getElementById("login-form") as HTMLFormElement;
function handleSubmit(event: SubmitEvent): void {
event.preventDefault();
const data = new FormData(loginForm);
console.log("Form berhasil dikirim");
console.log(`Email: ${data.get("email")}`);
}
loginForm.addEventListener("submit", handleSubmit);
loginForm.requestSubmit();
/*
Output yang diharapkan:
> Form berhasil dikirim
> Email: [email protected]
*/
“Jalan Pintas” Berisiko: Kapan Menggunakan Non-null Assertion (!)
TypeScript menyediakan operator ! (non-null assertion) yang secara paksa memberi tahu TypeScript: “Percayai aku, nilai ini pasti tidak null.”
// Membuat tombol agar contoh ini lengkap dan bisa langsung dijalankan
const tombolBaru = document.createElement("button");
tombolBaru.id = "submit-btn";
tombolBaru.textContent = "Kirim";
document.body.appendChild(tombolBaru);
// Tanda ! berarti: "Aku yakin elemen ini tidak bernilai null"
const tombol = document.getElementById("submit-btn")!;
tombol.addEventListener("click", () => {
console.log("Tombol diklik");
});
tombol.click();
/*
Output yang diharapkan:
> Tombol diklik
*/
Kapan boleh digunakan?
- Ketika kamu memiliki pengetahuan di luar TypeScript bahwa nilai tersebut tidak akan pernah null
- Dalam kode test / setup
- Ketika bekerja dengan library eksternal yang tipenya kurang akurat
Kapan JANGAN digunakan?
// ❌ Ini berbahaya — jika elemen tidak ada, program crash dengan TypeError
// const elemen = document.getElementById("mungkin-tidak-ada")!;
// elemen.textContent = "Hello"; // TypeError: Cannot set properties of null
// ✅ Lebih aman: gunakan pengecekan if
const elemen = document.getElementById("mungkin-tidak-ada");
if (elemen !== null) {
elemen.textContent = "Hello";
console.log(elemen.textContent);
} else {
console.log("Elemen tidak ditemukan, tidak ada yang di-update");
}
/*
Output yang diharapkan (jika elemen tidak ada):
> Elemen tidak ditemukan, tidak ada yang di-update
*/
Anggap ! seperti tanda “Saya sudah periksa sendiri” — jika ternyata kamu salah, TypeScript tidak bisa menolongmu dan programmu akan crash dengan TypeError di runtime. Gunakan dengan sangat hemat.
Contoh Kasus Nyata
Kasus 1: Fetch Data dari API
Bayangkan kamu membangun aplikasi seperti Tokopedia — ada banyak data produk yang di-fetch dari API. Data yang kembali mungkin tidak selalu ada.
interface Produk {
id: number;
nama: string;
harga: number;
deskripsi: string | null; // Deskripsi boleh bernilai null
}
// Data contoh agar kode bisa langsung dijalankan tanpa API sungguhan
const daftarProduk: Produk[] = [
{
id: 1,
nama: "Keyboard Mekanik",
harga: 750000,
deskripsi: null,
},
{
id: 2,
nama: "Mouse Wireless",
harga: 350000,
deskripsi: "Mouse nirkabel dengan baterai tahan lama",
},
];
async function ambilProduk(id: number): Promise<Produk | null> {
// Mencari produk berdasarkan id, mengembalikan null jika tidak ditemukan
return daftarProduk.find((produk) => produk.id === id) ?? null;
}
async function tampilkanProduk(id: number): Promise<void> {
const produk = await ambilProduk(id);
// Early return: hentikan fungsi jika produk tidak ada
if (!produk) {
console.log(`Produk dengan id ${id} tidak ditemukan`);
return;
}
// Operator ?? memberi nilai default hanya jika deskripsi null atau undefined
const deskripsi = produk.deskripsi ?? "Deskripsi belum tersedia";
console.log(`Nama: ${produk.nama}`);
console.log(`Harga: Rp${produk.harga.toLocaleString("id-ID")}`);
console.log(`Deskripsi: ${deskripsi}`);
}
// Menampilkan produk yang ada (deskripsi null)
tampilkanProduk(1);
// Menampilkan produk yang ada (deskripsi terisi)
tampilkanProduk(2);
// Menampilkan produk yang tidak ada
tampilkanProduk(99);
/*
Output yang diharapkan:
> Nama: Keyboard Mekanik
> Harga: Rp750.000
> Deskripsi: Deskripsi belum tersedia
>
> Nama: Mouse Wireless
> Harga: Rp350.000
> Deskripsi: Mouse nirkabel dengan baterai tahan lama
>
> Produk dengan id 99 tidak ditemukan
*/
Kasus 2: Mengelola State dalam Aplikasi React
Bayangkan kamu membangun aplikasi React sederhana yang menggunakan state untuk menampilkan data pengguna:
import React, { useState } from "react";
interface Pengguna {
nama: string;
umur: number;
}
const App: React.FC = () => {
const [pengguna, setPengguna] = useState<Pengguna | null>(null);
const handleLogin = () => {
// Simulasi login pengguna
setPengguna({ nama: "John Doe", umur: 30 });
};
const handleLogout = () => {
setPengguna(null);
};
return (
<div>
{pengguna !== null ? (
<div>
<p>Nama: {pengguna.nama}</p>
<p>Umur: {pengguna.umur}</p>
<button onClick={handleLogout}>Logout</button>
</div>
) : (
<div>
<p>Tidak ada pengguna yang login</p>
<button onClick={handleLogin}>Login</button>
</div>
)}
</div>
);
};
export default App;
Dalam contoh ini, state pengguna bertipe Pengguna | null. TypeScript memaksa kita memeriksa apakah pengguna !== null sebelum mengakses pengguna.nama atau pengguna.umur. Ini mencegah crash jika komponen dirender sebelum data tersedia.
Kasus 3: Parsing Data dari localStorage
Skenario umum lain adalah membaca data dari localStorage, yang bisa mengembalikan null jika key tidak ditemukan:
interface Pengaturan {
tema: "terang" | "gelap";
bahasa: string;
}
function muatPengaturan(): Pengaturan {
const tersimpan = localStorage.getItem("pengaturan");
// tersimpan bisa null jika belum pernah disimpan
if (tersimpan === null) {
console.log("Pengaturan belum ada, menggunakan default");
return { tema: "gelap", bahasa: "id" };
}
try {
const hasil = JSON.parse(tersimpan) as Pengaturan;
console.log("Pengaturan berhasil dimuat:", hasil);
return hasil;
} catch {
console.log("Data pengaturan rusak, menggunakan default");
return { tema: "gelap", bahasa: "id" };
}
}
function simpanPengaturan(pengaturan: Pengaturan): void {
localStorage.setItem("pengaturan", JSON.stringify(pengaturan));
console.log("Pengaturan tersimpan:", pengaturan);
}
// Simpan lalu muat kembali
simpanPengaturan({ tema: "terang", bahasa: "id" });
const pengaturan = muatPengaturan();
console.log(`Tema aktif: ${pengaturan.tema}`);
/*
Output yang diharapkan:
> Pengaturan tersimpan: { tema: 'terang', bahasa: 'id' }
> Pengaturan berhasil dimuat: { tema: 'terang', bahasa: 'id' }
> Tema aktif: terang
*/
Ringkasan Teknik Penanganan Null
| Teknik | Kapan Digunakan | Hasil jika Null |
|---|---|---|
if (x !== null) | Perlu logika berbeda | Masuk ke blok else |
x?.properti | Hanya baca nilai | Mengembalikan undefined |
x ?? "default" | Butuh nilai pengganti | Mengembalikan nilai default |
| Early return | Guard di awal fungsi | Fungsi berhenti lebih awal |
x as Tipe | Yakin tipe spesifik | Tidak ada pengecekan runtime |
x! | Yakin 100% tidak null | Crash jika salah prediksi |
Referensi lengkap tentang konsep type narrowing tersedia di artikel memahami type narrowing dan type guards di TypeScript. Untuk memahami konfigurasi strictNullChecks lebih dalam, baca juga tutorial konfigurasi tsconfig.json.
Pertanyaan yang Sering Diajukan
Apa perbedaan antara null dan undefined di TypeScript?
Keduanya merepresentasikan “tidak ada nilai”, tapi semantiknya berbeda. undefined berarti variabel dideklarasikan tapi belum diberi nilai, sedangkan null berarti variabel sengaja diberi nilai “kosong/tidak ada”. Dalam praktik TypeScript, keduanya sering ditangani bersamaan menggunakan union type T | null | undefined, dan operator ?? (nullish coalescing) menangani keduanya sekaligus.
Bagaimana cara menonaktifkan strictNullChecks jika terasa terlalu ketat?
Kamu bisa menambahkan "strictNullChecks": false di tsconfig.json, tapi ini sangat tidak disarankan untuk proyek baru. Menonaktifkannya menghilangkan salah satu manfaat terbesar TypeScript dan membuat kode lebih rentan terhadap bug runtime. Lebih baik pelajari cara menanganinya dengan benar — investasi waktu yang sangat worth it.
Apa perbedaan optional chaining (?.) dan non-null assertion (!)?
Optional chaining ?. adalah cara aman yang mengembalikan undefined jika nilai kiri adalah null atau undefined — tidak ada crash. Sebaliknya, non-null assertion ! adalah pernyataan kepada TypeScript bahwa kamu yakin nilainya bukan null, tapi jika ternyata salah, programmu akan crash dengan TypeError di runtime. Gunakan ?. untuk keamanan, dan ! hanya sebagai pilihan terakhir.
Kapan sebaiknya saya menggunakan optional chaining (?.) vs pengecekan if?
Gunakan ?. ketika kamu hanya ingin membaca nilai dan tidak masalah jika hasilnya undefined. Gunakan pengecekan if ketika kamu perlu melakukan sesuatu — menjalankan logika, memanggil method, memperbarui tampilan — berdasarkan apakah nilainya ada atau tidak. Kombinasi ?. dengan ?? untuk nilai default adalah pola yang sangat umum dan idiomatis di TypeScript modern.
Mengapa TypeScript tidak langsung error saat kompilasi jika saya menggunakan !?
Operator ! adalah instruksi eksplisit kepada TypeScript untuk mempercayai developer. TypeScript menganggap kamu sudah tahu apa yang kamu lakukan — ini disebut “escape hatch” atau pintu keluar darurat dari sistem tipe. TypeScript tidak punya cara untuk memverifikasi kebenaran klaim tersebut di compile time, sehingga tanggung jawab ada di tangan developer. Jika salah digunakan, error akan muncul di runtime, bukan saat kompilasi.
Apakah && bisa digunakan sebagai alternatif pengecekan null?
Ya, pola nilai && nilai.properti berfungsi sebagai pengecekan, tapi ada jebakan: && juga menganggap 0, "", dan false sebagai falsy. Untuk pengecekan null/undefined yang presisi, lebih baik gunakan nilai !== null && nilai !== undefined, atau lebih singkat dengan optional chaining nilai?.properti. Ini mencegah bug halus ketika nilainya adalah angka nol yang valid.
Kesimpulan
Error “Object is possibly ‘null’” di TypeScript bukan musuh — ia adalah sistem keamanan yang melindungi kode produksimu dari crash yang tidak terduga. Intinya:
strictNullChecksmembuat TypeScript membedakan nilai yang bisanull/undefineddari yang tidak- Gunakan pengecekan
if(type guard) untuk logika bercabang berdasarkan ada/tidaknya nilai - Gunakan optional chaining (
?.) untuk akses properti yang mungkin tidak ada secara aman - Gunakan nullish coalescing (
??) untuk memberikan nilai default - Gunakan early return untuk membersihkan kode dan menghindari nesting yang dalam
- Gunakan non-null assertion (
!) hanya ketika kamu benar-benar yakin dan tidak ada pilihan lain
Dengan memahami dan menerapkan pola-pola ini, kode TypeScript-mu akan jauh lebih aman, lebih mudah di-maintain, dan lebih ekspresif dalam mengkomunikasikan niat kepada rekan satu tim.
Selamat berlatih, dan jangan takut dengan pesan error TypeScript — setiap error adalah pelajaran baru yang membuat kode kamu semakin solid! Terus eksplorasi artikel-artikel TypeScript lainnya di KamusNgoding untuk meningkatkan kemampuanmu lebih jauh.