APIs & Integrations

SHutchinson_
Member

Embed Chat Into NextJs (React SPA)

Hey all,

 

I'm looking for a recommended solution for embedding Hubspot Live Chat into a NextJs website. Note I'm using the NextJs App router.

 

Currently there's a thread with some community posted examples, but this thread is from 2020 with the most recent example posted in 2022. https://community.hubspot.com/t5/CMS-Development/Embed-Chat-Into-A-ReactJS-SPA/m-p/275754/thread-id/...

 

I've used examples from the above thread, to develop my own solution, which I've posted below.

 

Currently I am experiencing issues with some users starting chats and Hubspot reporting their name incorrectly. I'm unsure of the root cause but wanted to get clarification on whether the below integration code could be a contributing factor or not.

 

Would love to hear opinions/advice on this code and/or clarity on what the integration code should look like. Separately has anyone else experienced issues with support chats incorrectly identify users?

 

Inside _document.tsx between the <Head> and </Head> tags

 

<script type="text/javascript" dangerouslySetInnerHTML={{
  __html: `
    window.hsConversationsSettings = {
      loadImmediately: false
    }
  `
 }}
/>

 

 

Inside _app.tsx (the usage)

 

import Script from 'next/script'
import { HubspotConversationsProvider } from 'components/HubspotConversationsProvider/HubspotConversationsProvider'

export default function App(props) {
  const {
    Component,
    pageProps
  } = props
  return (
    <>
      <Script
        src="https://js-na1.hs-scripts.com/your-id-here.js"
        strategy="lazyOnload"
        id="hs-script-loader"
      />
      <HubspotConversationsProvider>
        <Component {...pageProps} />

        <ChatWidgetButton />
      </HubspotConversationsProvider>
    </>
  )
}

function ChatWidgetButton() {
  const { showWidget } = useHubspotConversations()

  return <button onClick={showWidget}>Toggle chat</button>
}

 

 

The provider: HubspotConversationsProvider.tsx

 

import { useRouter } from 'next/router'
import { createContext, useCallback, useContext, useEffect, useState } from 'react'

import { useAccount } from 'hooks/useAccount'
import useAccountState from 'hooks/useAccountState'
import useHubspotConversationsToken from 'hooks/useHubspotConversationsToken'

interface HubspotConversationsContextType {
  showWidget: () => void
  removeWidget: () => void
}

const HubspotConversationsContext = createContext<HubspotConversationsContextType | null>(null)

interface Props {
  children?: React.ReactNode
}

export const HubspotConversationsProvider = ({ children }: Readonly<Props>) => {
  const router = useRouter()
  const [isReady, setIsReady] = useState(false)

  const { data: accountState } = useAccountState()
  const { isAuthenticated } = accountState || {}
  const { data: account } = useAccount()

  const hubspotToken = useHubspotConversationsToken()

  const showWidget = useCallback(() => {
    if (!isReady) return
    window.HubSpotConversations.widget.open()
  }, [isReady])

  const onConversationsReady = useCallback(() => {
    setIsReady(true)
  }, [])

  useEffect(
    function init() {
      if (window.HubSpotConversations) {
        onConversationsReady()
      } else {
        window.hsConversationsOnReady = [onConversationsReady]
      }
    },
    [onConversationsReady],
  )

  useEffect(
    function updateSettingsAndLoadWidget() {
      if (isReady) {
        if (isAuthenticated && account?.email) {
          if (hubspotToken.isFetched && hubspotToken?.data) {
            const userWasPreviouslyNotLoggedIn =
              !window?.hsConversationsSettings?.identificationEmail && window?.hsConversationsSettings?.loadedPreviously

            const tokenHasChanged = window?.hsConversationsSettings?.identificationToken !== hubspotToken?.data

            if (userWasPreviouslyNotLoggedIn || tokenHasChanged) {
              // if you were previously logged in, but now you are not...
              window.HubSpotConversations.clear({ resetWidget: true })
            }

            window.hsConversationsSettings = {
              loadImmediately: false,
              identificationEmail: account?.email,
              identificationToken: hubspotToken.data,
              loadedPreviously: true,
            }

            const status = window.HubSpotConversations.widget.status()

            if (status.loaded) {
              window.HubSpotConversations.widget.refresh()
            } else {
              window.HubSpotConversations.widget.load()
              window.HubSpotConversations.widget.refresh()
              // we might need to call refresh here to update the url
            }
          } else {
            // Note: if there's an error retrieving the hubspot token data, we'll be here... it might be worth adding a backup load/refresh...???
          }
        } else {
          const userWasPreviouslyLoggedIn = !!window?.hsConversationsSettings?.identificationEmail

          if (userWasPreviouslyLoggedIn) {
            // if you were previously logged in, but now you are not...
            window.HubSpotConversations.clear({ resetWidget: true })
          }

          window.hsConversationsSettings = {
            loadImmediately: false,
            loadedPreviously: true,
          }

          const status = window.HubSpotConversations.widget.status()

          if (status.loaded) {
            window.HubSpotConversations.widget.refresh()
          } else {
            window.HubSpotConversations.widget.load()
            window.HubSpotConversations.widget.refresh()
            // we might need to call refresh here to update the url
          }
        }
      }
    },
    [isReady, isAuthenticated, hubspotToken.data, hubspotToken.isFetched, account?.email],
  )

  useEffect(() => {
    const handler = () => {
      if (isReady) {
        const status = window.HubSpotConversations.widget.status()

        if (status.loaded) {
          window.HubSpotConversations.widget.refresh()
        } else {
          window.HubSpotConversations.widget.load()
          window.HubSpotConversations.widget.refresh()
        }
      }
    }

    router.events.on('routeChangeComplete', handler)

    return () => {
      router.events.off('routeChangeComplete', handler)
    }
  }, [isReady, router.events])

  const removeWidget = useCallback(() => {
    if (!isReady) return
    window.HubSpotConversations.widget.remove()
  }, [])

  return (
    <HubspotConversationsContext.Provider
      // -ignore
      value={{
        showWidget,
        removeWidget,
      }}
    >
      {children}
    </HubspotConversationsContext.Provider>
  )
}

export function useHubspotConversations() {
  const context = useContext(HubspotConversationsContext)

  if (context === null) {
    throw new Error('useHubspotConversations must be used within HubspotConversationsProvider')
  }

  return context
}

declare global {
  interface Window {
    hsConversationsSettings: Record<string, any>
    hsConversationsOnReady: Array<() => void>
    HubSpotConversations: {
      clear: any
      on: any
      off: any
      widget: {
        status: () => { loaded: boolean; pending: boolean }
        load: (params?: { widgetOpen: boolean }) => void
        remove: () => void
        open: () => void
        close: () => void
        refresh: (params?: { openToNewThread: boolean }) => void
      }
    }
  }
}

 

0 Upvotes
4 Replies 4
Kevin-C
Recognized Expert | Partner
Recognized Expert | Partner

Embed Chat Into NextJs (React SPA)

Hey @SHutchinson_ could you elaborate on the misidentification? What patterns are you seeing?

Kevin Cornett - Sr. Solutions Architect @ BridgeRev
0 Upvotes
SHutchinson_
Member

Embed Chat Into NextJs (React SPA)

Hey @Kevin-C 

 

Note we currently use Slack and have the Slack - Hubspot Chat integration.

 

We've had instances of users creating a covnersation thread in live chat, then we receive a notification in Slack saying "FirstName LastName started a conversation through Live Chat". Our team then replies with something like "Hey FirstName! How can I help today?". The user then responds "My name is SomethingElse :D".

 

For now we've instructed our support team to not mention anyone's name, but I'd like to track down the root-cause of the issue.

 

I've had a look in of the Hubspot Contacts where this issue has occurred and this single contact seems to have 9 different email addresses associated with them. They should only have the 1, so it looks like something is stitching these other email addresses to the contact incorrectly. Any ideas how I could possibly identify the source of the stitching process to attempt to address? (in this particular case each of the 9 email addresses should be there own contact)

 

0 Upvotes
SHutchinson_
Member

Embed Chat Into NextJs (React SPA)

I've attempted to dig deeper. I've reviewed the "Activity" log of this particular contact. I've identified a few cases where a Form submission has occurred with the details indicating that 1 property (an email) was updated. It looks like Hubspot is capturing the email from a non-Hubspot form (the website's login form) and updating the Contact's email addresses.

 

Any idea how Hubspot is determining which Contact it should associate the email address with, once someone logs in? (is the chat connected to this? e.g. if someone uses the same machine to login with one account, then logout and login to another account... does Hubspot stitch both email addresses to a single Contact?)

0 Upvotes
Jaycee_Lewis
Community Manager
Community Manager

Embed Chat Into NextJs (React SPA)

Hey, @SHutchinson_ 👋 Thanks for your post. Adding your details and code example is always appreciated. Let's invite some of our community experts to the converstaion — hey @albertsg @nikodev @Kevin-C @miljkovicmisa do you have any suggestions or troubleshooting tips for @SHutchinson_?

 

Thank you very much for taking a look! — Jaycee


HubSpot’s AI-powered customer agent resolves up to 50% of customer queries instantly, with some customers reaching up to 90% resolution rates.
Learn More.


Did you know that the Community is available in other languages?
Join regional conversations by changing your language settings !