Optional Chaining
Cuando trabajamos con el DOM, a veces un elemento puede no existir. Si intentamos acceder a métodos o propiedades de un elemento que es null, obtenemos un error. El optional chaining (?.) nos ayuda a evitar estos errores.
El problema: Elementos que no existen
Imagina que intentamos seleccionar un botón que no está en la página:
<!DOCTYPE html>
<html lang="es">
<head>
<script type="module">
const button = document.querySelector('#boton-importante')
button.addEventListener('click', function () {
console.log('Click')
})
</script>
</head>
<body>
<h1>Mi página</h1>
<!-- ❌ No hay ningún botón con id="boton-importante" -->
</body>
</html>
❌ Error:
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
¿Por qué falla?
const button = document.querySelector('#boton-importante')
console.log(button) // null
// Intentamos hacer null.addEventListener()
button.addEventListener('click', function () {
/* ... */
})
// ❌ Error: No puedes llamar métodos en null
Solución tradicional: Verificar con if
La forma tradicional es verificar si el elemento existe antes de usarlo:
const button = document.querySelector('#boton-importante')
if (button) {
button.addEventListener('click', function () {
console.log('Click en el botón')
})
}
✅ Ahora funciona:
- Si
buttonexiste → Se añade el evento - Si
buttonesnull→ No hace nada (no hay error)
Ejemplo con múltiples verificaciones:
const button = document.querySelector('#boton-importante')
if (button) {
button.addEventListener('click', function () {
console.log('Click')
})
}
const input = document.querySelector('#mi-input')
if (input) {
input.addEventListener('input', function () {
console.log('Escribiendo...')
})
}
const form = document.querySelector('#mi-formulario')
if (form) {
form.addEventListener('submit', function (event) {
event.preventDefault()
})
}
Problema: Mucho código repetitivo con los if.
Optional Chaining: La solución moderna
El optional chaining (?.) es un operador que verifica automáticamente si algo existe antes de acceder a sus propiedades o métodos.
Sintaxis:
objeto?.propiedad
objeto?.metodo()
¿Cómo funciona?
const button = document.querySelector('#boton-importante')
// Con optional chaining
button?.addEventListener('click', function () {
console.log('Click')
})
Comportamiento:
- Si
buttonexiste (no esnullniundefined) → EjecutaaddEventListener - Si
buttonesnulloundefined→ No hace nada (no hay error)
Comparación: Con y sin optional chaining
Sin optional chaining:
const button = document.querySelector('#boton-importante')
if (button) {
button.addEventListener('click', function () {
console.log('Click')
})
}
Con optional chaining:
const button = document.querySelector('#boton-importante')
button?.addEventListener('click', function () {
console.log('Click')
})
Resultado: Exactamente el mismo comportamiento, pero con menos código.
Optional chaining con propiedades
No solo funciona con métodos, también con propiedades:
const button = document.querySelector('#boton-importante')
// Acceder a propiedades
console.log(button?.textContent) // undefined si no existe
console.log(button?.id) // undefined si no existe
console.log(button?.disabled) // undefined si no existe
// Cambiar propiedades
button?.classList.add('activo')
button?.setAttribute('data-applied', 'true')
Optional chaining encadenado
Puedes usar ?. múltiples veces en cadena:
const container = document.querySelector('#container')
// Acceder a propiedades anidadas de forma segura
const width = container?.firstChild?.getBoundingClientRect()?.width
console.log(width) // undefined si cualquier parte es null
Sin optional chaining:
let width
if (container) {
if (container.firstChild) {
const rect = container.firstChild.getBoundingClientRect()
if (rect) {
width = rect.width
}
}
}
console.log(width)