plain-scooter-8648
03/16/2026, 3:45 PMsetAttributes() when the heap ID is avalible. Is this the wrong approach? Should I just wait until we have all the attributes first and then setup the growthbook object? The documentation seemed to indicate that assignment would be reevaluated when setAttributes is gets called.
I have started tracking the set Attributes call and only like 1% of the users that should have an expirement assigned are getting a tracking callback.
Here is my full growthbook setup script:
import { GrowthBook } from '@growthbook/growthbook';
import { BrowserCookieStickyBucketService } from '@growthbook/growthbook';
import Cookies from 'js-cookie';
import * as features from '@/scripts/store/features';
import { head } from 'underscore';
const growth_book = new GrowthBook({
apiHost: '<https://cdn.growthbook.io>',
clientKey: 'sdk-bFMPGDFC9sMwhjsE',
enableDevMode: process.env.NODE_ENV === 'development',
subscribeToChanges: true,
stickyBucketService: new BrowserCookieStickyBucketService({
jsCookie: Cookies,
}),
trackingCallback: (experiment, result) => {
console.log('viewed experiment', experiment.key, result.key);
window.heap?.track('Viewed Experiment', {
experimentId: experiment.key,
variationId: result.key,
});
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'viewed_experiment',
experimentId: experiment.key,
variationId: result.key,
});
},
});
window.gb = growth_book; // Expose for debugging
window.growth_book = growth_book; // Expose for debugging
//===========================
// Collect user attributes
//===========================
//should match tailwind.config.js
const screens = {
sm: '700px',
md: '1000px',
lg: '1150px',
xl: '1400px',
'2xl': '1600px',
};
const screenSize = Object.keys(screens)
.reverse()
.find(key => {
return window.innerWidth > parseInt(screens[key]);
});
const getHeapIdFromStorage = function () {
try {
const appId = window.heap?.appid;
const key = appId ? `_hp5_meta.${appId}` : Object.keys(localStorage).find(k => k.startsWith('_hp5_meta.'));
if (key) {
const meta = JSON.parse(localStorage.getItem(key));
return meta?.value?.userId || null;
}
} catch (e) {}
return null;
};
const waitForHeapId = function () {
const storedId = getHeapIdFromStorage();
if (storedId) {
setGbAttributes(storedId);
return Promise.resolve();
}
return new Promise(resolve => {
const timeout = setTimeout(() => {
console.warn('heapReady timeout — proceeding without Heap ID');
setGbAttributes(getHeapIdFromStorage());
resolve();
}, 5000);
window.addEventListener('heapReady', function () {
clearTimeout(timeout);
const heapId = getHeapIdFromStorage();
if (heapId) {
setGbAttributes(heapId);
resolve();
} else {
// heapReady can fire before userId is written — wait briefly for new visitors
setTimeout(() => {
setGbAttributes(window.heap?.getUserId() || getHeapIdFromStorage());
resolve();
}, 500);
}
});
});
};
const setGbAttributes = function (id) {
const attributes = {
id: id,
heapId: id || 'unknown',
email: window?.userAttributes?.email,
customerId: window?.userAttributes?.customerId,
totalSpent: window?.userAttributes?.totalSpent,
ordersCount: window?.userAttributes?.ordersCount,
tags: window?.userAttributes?.tags,
country: Shopify.country,
locale: Shopify?.locale,
currency: Shopify?.currency?.active,
url: window.location.href,
userAgent: navigator.userAgent,
screenWidth: window.innerWidth,
screenSize: screenSize,
themeId: Shopify.theme.id,
themeName: Shopify.theme.name,
themeRole: Shopify.theme.role,
params: paramsToObject(new URL(document.location).searchParams),
};
growth_book.setAttributes(attributes);
try {
window.heap?.track('growthbook:attributes', attributes);
} catch (e) {
console.log('heap not ready?');
}
};
//===========================
// setup growthbook
//===========================
function paramsToObject(entries) {
const result = {};
for (const [key, value] of entries) {
// each 'entry' is a [key, value] tupple
result[key] = value;
}
return result;
}
export const setupGrowthbook = async function () {
window.dispatchEvent(new Event('growthbook:setup'));
try {
window.heap?.track('growthbook:setup');
} catch (e) {
console.log('heap not ready?');
}
console.log('setup growthbook');
await waitForHeapId();
console.log('heap up');
// Wait for features to be available
await growth_book.init({ timeout: 1000 });
for (const key in features) {
const feature = features[key];
const value = growth_book.getFeatureValue(feature.key);
if (typeof value != 'undefined') {
feature.init(value);
}
}
growth_book.setRenderer(() => {
for (const key in features) {
const feature = features[key];
const value = growth_book.getFeatureValue(feature.key);
if (typeof value != 'undefined') {
feature.init(value);
}
}
});
console.log('growthbook ready');
//publish event that growthbook is ready
window.dispatchEvent(new Event('growthbook:ready'));
try {
window.heap?.track('growthbook:ready');
} catch (e) {
console.log('heap not ready?');
}
};plain-scooter-8648
03/16/2026, 3:55 PMastonishing-engineer-93454
04/15/2026, 9:11 PM