Skip to content
This repository has been archived by the owner on Apr 28, 2022. It is now read-only.

TypeError: window.heap.push is not a function. #605

Open
JustinTRoss opened this issue May 22, 2019 · 16 comments
Open

TypeError: window.heap.push is not a function. #605

JustinTRoss opened this issue May 22, 2019 · 16 comments

Comments

@JustinTRoss
Copy link

If loading Heap separately from Segment, when identifying with Heap (window.heap.identify), the page crashes with error: TypeError: window.heap.push is not a function.

While there's the obvious option of deferring all Heap usage to Segment, it seems like a bug to force the user down that path and doubly so to crash the page ambiguously.

Thoughts?

@Sbphillips19
Copy link

@JustinTRoss did you ever figure out a way around this. I was hoping this would work:

window.heap && window.heap.identify(data.name);

Can't seem to get around this error

@JustinTRoss
Copy link
Author

I can't recall exactly what we did. My guess is that we ended up abandoning segment.

If I'm recalling correctly, your proposal wouldn't work because segment actually rewrote the heap object somehow. window.heap.identify still exists, but window.heap.push does not. You could of course condition function call on existence of window.heap.push, but you would likely then just not identify with heap or not identify reliably, the partial data from which would likely be worthless.

@Sbphillips19
Copy link

Sbphillips19 commented Mar 26, 2020

@JustinTRoss just to be clear what do you mean by abandoning segment. Did you just get rid of using heap.io?

I tried doing this as well:

window.heap.loaded && window.heap.identify(data.name);

But it then just doesn't load the page. I feel like maybe this is an issue with compatibility with react? I just don't get why window.heap.push doesn't exist. Been just trying anything I can think of.

@JustinTRoss
Copy link
Author

I was using Heap at the time, and I was trying out Segment. I believe I resolved to abandon Segment and just keep using Heap as I had been. I'm sure an equally appropriate option would have been to switch fully to Segment and stick to only the features they expose. Here is their instruction on Heap identification: https://segment.com/docs/connections/destinations/catalog/heap/#identify. If I recall correctly, it's slightly restrictive in functionality.

Whatever is happening, window.heap as initialized by Segment renders parts of native Heap functionality unusable, presumably by actually mutating the Heap object's api.

@Sbphillips19
Copy link

@JustinTRoss so actually realized this issue and it was extremely stupid/ maybe similar to what you were doing (loading 2 libraries)- but our marketing guy added in heap to GTM so it was getting loaded twice. He added it in because we are using webflow for the main page. I had no idea it was added there as I didn't see any code in the webapp outside the script. Basically it was getting loaded twice/ causing issues. Stupid error, but maybe will help someone else if they read this

@hinok
Copy link

hinok commented Mar 28, 2020

I'm coming from google and I will try explain how this error occurs and how to avoid it.

It's starting with the default Heap snippet code that can be found in Heap's documentation Installation Guides:

<script type="text/javascript">
  window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};
  heap.load("YOUR_APP_ID");
</script>

which can be easily translated to much more readable version below

window.heap = window.heap || []

heap.load = function (appId, heapConfig) {
    window.heap.appid = appId;
    window.heap.config = heapConfig || {};

    const script = document.createElement("script");
    script.type = "text/javascript";
    script.async = true;
    script.src = "https://cdn.heapanalytics.com/js/heap-" + appId + ".js";
    
    const firstScript = document.getElementsByTagName("script")[0];
    firstScript.parentNode.insertBefore(script, firstScript);
    
    const cloneArray = (arrayLike) => Array.prototype.slice.call(arrayLike, 0);

    const createMethod = function (method) {
        return function () {
            heap.push([
                method,
                ...cloneArray(arguments)
            ]);
        }
    };

    const methods = [
        'addEventProperties',
        'addUserProperties',
        'clearEventProperties',
        'identify',
        'resetIdentity',
        'removeEventProperty',
        'setEventProperties',
        'track',
        'unsetEventProperty',
    ];
    
    for (let method of methods) {
        heap[method] = createMethod(method)
    }
};

heap.load("YOUR_APP_ID");

The problem is that the snippet from the Heap defines window.heap as an array

window.heap = window.heap || []

but when the heap script defined here

script.src = "https://cdn.heapanalytics.com/js/heap-" + appId + ".js";

is fully loaded, window.heap is NOT an array anymore, now it's an object.

The snippet also create methods that access global heap variable by using heap.push

var n = function (e) {
  return function () {
    heap.push([e].concat(Array.prototype.slice.call(arguments, 0)))
  }
}

When this problem may occurs?

When you pass window.heap as a function argument like below

function trackUser(heap, user) {
  heap.identify(user.id);
}

// ...
trackUser(window.heap, authenticatedUser);

especially when you do also some async stuff like

async function trackUser(heap, getUser) {
  const user = await getUser(); // <------- Because it's async, heap script may be already loaded and now it's an object

  if (user) {
     // Code below will execute heap.push which throws an error...
     // because global heap is the object now, not an array.
     heap.identify(user.id);
  }
}

// ...
trackUser(window.heap); // Here is passed an array version of window.heap from the code snippet

How to fix this error?

Don't pass heap as a function argument or pass a function that returns window.heap.

Avoid

trackUser(window.heap, authenticatedUser);

Better

trackUser(() => window.heap, authenticatedUser);
async function trackUser(getHeap, getUser) {
  const user = await getUser();

  if (user) {
     const heap = getHeap();
     heap.identify(user.id);
  }
}

// ...
trackUser(getHeap, getUser);

or just use window.heap directly but then code testing is a bit more harder...

async function trackUser(getUser) {
  const user = await getUser();

  if (user) {
     window.heap.identify(user.id);
  }
}

// ...
trackUser(getUser);

I hope that it will help someone.

heap-explained

@JustinTRoss
Copy link
Author

Thanks for the detailed explanation @hinok ! It seems like it could be a functional workaround. Ideally, Segment would just stop mutating other libs, so I'm going to leave this open in hope that a root resolution might one day be found.

@quantizor
Copy link

Why was this closed @pooyaj? I'm seeing the issue actively.

@pooyaj
Copy link
Contributor

pooyaj commented Jul 31, 2020

@probablyup do you have a live website or a sandbox with the issue for us to debug?

@pooyaj pooyaj reopened this Jul 31, 2020
@quantizor
Copy link

The error is in a protected part of our website, so I can't link. The reproduction though is loading the heap client via segment and then calling heap.identify() which internally uses heap.push which segment I guess overwrites, or possibly an old version of the client is loaded by segment.

@pooyaj
Copy link
Contributor

pooyaj commented Jul 31, 2020

👍 Let us repro, and get back!

@hinok
Copy link

hinok commented Aug 1, 2020

@probablyup @pooyaj We don't use segment in our application but we use heap and we've had exactly the same issue. The root of the problem is the way how heap instance is created in the official code snippet before the heap script is fully loaded. The problem appears always when you pass a reference of heap before the script is loaded.

Briefly looking at https://github.com/segmentio/analytics.js-integrations/blob/master/integrations/heap/lib/index.js#L35
it seems that the same problem is in this integration.

Script is NOT loaded: it's just an array with methods
Script is fully loaded: it's an object (doesn't have anymore .push and array in prototype chain)

You can try and recreate a demo using code from my comment #605 (comment)

@JavierPiedra
Copy link

I think I got a solution for this (using react and heap)
The problem is that if you look at the dev tools, it's possible you are calling several times heap. You can notice if you have several scripts loaded.
image

My solution using react, and a persisiting layout through route changes, is to load heap just once using a function like this:

export function loadAndSetScript(innerHtml, position, id) {
  if (!position) {
    return;
  }

  const script = document.createElement('script');
  script.setAttribute('async', '');
  script.setAttribute('id', id);
  script.type = 'text/javascript';
  script.innerHTML = innerHtml;
  position.appendChild(script);
}

then calling this fucntion in your persisiting layout component, you can do something like this:

import React, { useRef } from 'react';
const PersistingLayout = ({ prop1, prop2 }) => {
....
....
  const loaded = useRef();
  if (typeof window !== 'undefined' && !loaded.current) {
    if (!document.querySelector('#heap')) {
      loadAndSetScript(
        `window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};
  heap.load(`${yourHeapNumber}`);`,
        document.querySelector('head'),
        'heap'
      );
    }
    loaded.current = true;
  }
.....
......

@JavierPiedra
Copy link

Thanks for the detailed explanation @hinok ! It seems like it could be a functional workaround. Ideally, Segment would just stop mutating other libs, so I'm going to leave this open in hope that a root resolution might one day be found.

The problem I found is that heap is being called different times. This solves it.

@richardtemple29
Copy link

@Sbphillips19 - 1000 thank you's to you! I've been trying to use heap to track scrolling. Your note about heap being loaded twice because marketing added it in GTM is exactly what happened to me, i've been trying to get this to work consistently for a few hours, and the loading issue was causing it to raise the exception "heap.push is not a function" about 95% of the time. The 5% of the time it worked was really throwing me off course! Anyway, i removed the heap snippet and then was able to track scrolling.

@alecbw
Copy link

alecbw commented Nov 27, 2021

Just to add to this thread:

One potential reason you may be seeing this error and having a hard time debugging it:

The initializing script is being run somewhere else.

For example, if you're adding it to Google Tag Manager and it already exists in your frontend codebase (or vice versa).

To check, just run heap in the DevTools console of your browser on your page.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants