[Bug]: Uncaught (in promise) TypeError: Cannot read properties of null (reading 'appendChild')
mrleblanc101 opened this issue · comments
Expected Behavior
I'm trying to implement CookieConsent with GTM Consent Mode.
Here is my config:
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
gtag('consent', 'default', {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
functionality_storage: 'denied',
personalization_storage: 'denied',
security_storage: 'denied',
});
gtag('consent', 'update', {
functionality_storage: CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied',
ad_storage: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
ad_user_data: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
ad_personalization: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
analytics_storage: CookieConsent.acceptedCategory('analytics') ? 'granted' : 'denied',
personalization_storage: CookieConsent.acceptedCategory('personalization') ? 'granted' : 'denied',
security_storage: CookieConsent.acceptedCategory('security') ? 'granted' : 'denied',
});
CookieConsent.run({
guiOptions: {
consentModal: {
layout: 'box wide',
position: 'bottom right',
equalWeightButtons: true,
flipButtons: true,
},
preferencesModal: {
layout: 'box',
position: 'right',
equalWeightButtons: true,
flipButtons: false,
},
},
categories: {
necessary: {
readOnly: true,
},
marketing: {},
analytics: {},
functionality: {},
personalization: {},
security: {},
},
language: { ... }
onConsent: (...args) => {
manageConsent(args);
window.dataLayer.push({ event: 'consent_ready' });
},
onChange: (...args) => {
manageConsent(args);
window.dataLayer.push({ event: 'consent_update' });
},
});
function manageConsent() {
gtag('consent', 'update', {
functionality_storage: CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied',
ad_storage: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
ad_user_data: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
ad_personalization: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
analytics_storage: CookieConsent.acceptedCategory('analytics') ? 'granted' : 'denied',
personalization_storage: CookieConsent.acceptedCategory('personalization') ? 'granted' : 'denied',
security_storage: CookieConsent.acceptedCategory('security') ? 'granted' : 'denied',
});
}
</script>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-XXXXXXXX');</script>
Current Behavior
I understand that the errors is because the plugin try to insert the banner into the DOM, while the page has not finished loading (DOMContentLoaded).
My issue is that if I put CookieConsent.run(...)
inside a window.addEventListener('load', () => { ... })
, the Container Loaded
event will be fired before GTM receive the consent value from gtag()
inside onConsent
, which mean Facebook Pixel is blocked even if the user clicked Accept All
.
Steps to reproduce
.
Proposed fix or additional info.
Would it be possible to execute the "cc.run()" command as soon as possible, and only delay the insertion of the banner into the DOM once the page is loaded ? Currently, I need to wrap the whole cc.run()
inside the window.addEventListener('load', () => { ... })
which delay the plugin initialization which does not require access to DOM and could be executed earlier.
Version
3.0.1
On which browser do you see the issue?
Chrome
Also, if I run CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied'
before CookieConsent.run(...)
, it will always return denied even if the cookie is set !
Should I move all my tags from All pages
trigger to DOM Ready
or Window Loaded
instead ?
This seems odd as All pages
is the default for GA4 when using to Google Template.
Would it be possible to execute the "cc.run()" command as soon as possible
By default the plugin is loaded asynchronously, but you can change this behaviour via the lazyHtmlGeneration option.
Also, if I run CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied' before CookieConsent.run(...), it will always return denied even if the cookie is set !
Yes, that's the expected behaviour; the only method that can be run before the plugin's own initialization is getCookie. You can access the acceptedCategories before the plugin's initialization like this:
const acceptedCategories = CookieConsent.getCookie('categories', 'cc_cookie') || [];
// acceptedCategories.includes('analytics')
CookieConsent.run({...});
By default the plugin is loaded asynchronously, but you can change this behaviour via the lazyHtmlGeneration option.
Maybe I'm not understanding correctly, lazyHtmlGeneration
seems to default to true
in the documentation, yet it I still have the error. I also tried explicitely:
CookieConsent.run({
autoShow: false,
lazyHtmlGeneration: true,
...
});
Yes, that's the expected behaviour; the only method that can be run before the plugin's own initialization is getCookie. You can access the acceptedCategories before the plugin's initialization like this:
Thanks, I guess that will do, but I think it would be nice to decouple initialization
and injection
into the DOM.
I thought about reading the cookie manually using a cookie library like js-cookie
, but I thought it was overkill.
Nice to know I can use CookieConsent.getCookie
before init
.
Let me know If you want me to close this or you'd like to keep it open for doc/exemple update.
Here is my final results if it can help someone else:
<script src="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.umd.js"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
gtag('consent', 'default', {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
functionality_storage: 'denied',
personalization_storage: 'denied',
security_storage: 'denied',
});
if(CookieConsent.getCookie('categories', 'cc_cookie')) {
const acceptedCategories = CookieConsent.getCookie('categories', 'cc_cookie') || [];
gtag('consent', 'update', {
functionality_storage: acceptedCategories.includes('functionality') ? 'granted' : 'denied',
ad_storage: acceptedCategories.includes('marketing') ? 'granted' : 'denied',
ad_user_data: acceptedCategories.includes('marketing') ? 'granted' : 'denied',
ad_personalization: acceptedCategories.includes('marketing') ? 'granted' : 'denied',
analytics_storage: acceptedCategories.includes('analytics') ? 'granted' : 'denied',
personalization_storage: acceptedCategories.includes('personalization') ? 'granted' : 'denied',
security_storage: acceptedCategories.includes('security') ? 'granted' : 'denied',
});
}
window.addEventListener('load', function () {
CookieConsent.run({
guiOptions: {
consentModal: {
layout: 'box wide',
position: 'bottom right',
equalWeightButtons: true,
flipButtons: true,
},
preferencesModal: {
layout: 'box',
position: 'right',
equalWeightButtons: true,
flipButtons: false,
},
},
categories: {
necessary: {
readOnly: true,
},
marketing: {},
analytics: {},
functionality: {},
personalization: {},
security: {},
},
language: { ... },
onFirstConsent: (...args) => {
manageConsent(args);
window.dataLayer.push({ event: 'consent_ready' });
},
onChange: (...args) => {
manageConsent(args);
window.dataLayer.push({ event: 'consent_update' });
},
});
});
function manageConsent() {
gtag('consent', 'update', {
functionality_storage: CookieConsent.acceptedCategory('functionality') ? 'granted' : 'denied',
ad_storage: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
ad_user_data: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
ad_personalization: CookieConsent.acceptedCategory('marketing') ? 'granted' : 'denied',
analytics_storage: CookieConsent.acceptedCategory('analytics') ? 'granted' : 'denied',
personalization_storage: CookieConsent.acceptedCategory('personalization') ? 'granted' : 'denied',
security_storage: CookieConsent.acceptedCategory('security') ? 'granted' : 'denied',
});
}
</script>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-XXXXXXXX');</script>
Perhaps I wasn't clear, you should set lazyHtmlGeneration
to false to run the plugin synchronously.
We can leave this open, until there is a docs. section on how to set up GTM.
@orestbida I tried false
too and i had the exact same issue, but anyway I posted my solution above.
Thanks again for your help