Explain Codes LogoExplain Codes Logo

How to wait until an element exists?

javascript
async-await
mutation-observer
element-detection
Alex KataevbyAlex Kataev·Aug 13, 2024
TLDR

You'll execute code when a DOM element emerges using MutationObserver API. This efficient and lean method doesn't require jQuery or timers. Here's a crisp snippet that does the magic:

const waitForElement = (selector, callback) => { const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if(node.matches && node.matches(selector)) { callback(node); observer.disconnect(); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }; // Usage waitForElement('#myElement', element => { // Actions to perform once '#myElement' becomes part of the DOM lineup console.log('Element says hi:', element); });

Keep reading for detailed explanations, alternative techniques, and fallback options.

Getter with the latest methods

Async/Await along promises

Let's turn the code into a promise which allows you to use await within an async function smoothly.

const elementReady = selector => new Promise(resolve => { const observer = new MutationObserver((mutations, obs) => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); // Promise kept! Element found. obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); // Usage with async/await (async () => { const element = await elementReady('#myElement'); // Element-specific logic console.log('Async/await: Element is now on-board:', element); })();

Backup with DOMNodeInserted

MutationObserver not supported? No worries. This fallback to DOMNodeInserted event helps out in case you are dealing with ancient browsers.

function backupOnElementReady(selector, callback) { document.addEventListener('DOMNodeInserted', function(event) { if (event.target.matches(selector)) { callback(event.target); // It's Here! } }); }

Though, DOMNodeInserted might get too chatty and fire rapidly, so approach with caution.

Old school approaches

Interval based polling approach

If you like setInterval more than MutationObserver, no problem. We can periodically check for the element's existence.

const checkForElement = (selector, callback, interval = 100) => { const existenceCheck = setInterval(() => { const element = document.querySelector(selector); // Element works on its own time. if (element) { callback(element); clearInterval(existenceCheck); } }, interval); };

Beware, excessive checking or complex page structure might put a dent in your performance.

A timeout for the impatient

Imagine waiting indefinitely for an element that forgets to show up? Doesn't sound fun, right? Here's how we implement a timeout for that:

const waitForElementWithTimeout = (selector, callback, timeout = 3000) => { const startTime = Date.now(); const interval = setInterval(() => { const element = document.querySelector(selector); const elapsedTime = Date.now() - startTime; if (element) { callback(element); clearInterval(interval); } else if (elapsedTime > timeout) { console.warn(`Waiting for element ${selector} has tested your patience for ${timeout}ms.`); clearInterval(interval); } }, 100); };

Save precious time with the inclusion of an appropriate timeout message.

Finishing up post-arrival

Once the element gets detected, to tie it in with the rest of your application flow, you might need to execute more functions or steps.

waitForElement('#myElement', element => { doTheRestOfTheStuff({ element, // This hard working element gets passes along to the next function additionalParam: 'value', // Chuck in any other param you need }); });

Transform your workflow to a busy line of factory workstations, where detection of the element signals it's time to start the next process.