How to Add AI Customer Support to a React App

A tested install for React apps: the useEffect hook that loads the AssistLoop chat once from your main component, the Next.js version, and the fixes for a missing or doubled chat button.

AssistLoop Team12 min read
How to Add AI Customer Support to a React App
On this page

In a React app the chat belongs in your code, not pasted into a page, and the obvious first attempt loads it twice in development. This guide adds AssistLoop to a React app with one useEffect in your main component. An AI support agent that learned your own site then answers on every page, and keeps the conversation as visitors move around. It also covers Next.js and the two mistakes that leave you with no chat button or two of them; budget fifteen minutes, mostly for testing.

Last updated: October 2026. We built this guide in a small React 19 app on Vite, recorded in mid-October 2026. On 23 October 2026 we ran the hook again in a fresh copy of the app, and the Script version in a Next.js 15.5 app. Both times we counted script tags and chat buttons before and after changing pages.

Quick answer
To add AI customer support to a React app, load AssistLoop's widget script once from your main component with a useEffect hook, and start it with your agent ID when the script loads.

  1. In AssistLoop, open Widget Installation, pick React / Vue / SPA and note the agent ID in the code card.

  2. In App.jsx, import useEffect and add the hook from Step 2 below, with an id check so it only runs once.

  3. Save, open the app and ask the chat something your site answers.

  4. Move to another page: the same chat and conversation stay put.

A customer on a pottery shop's care guide wants to know whether the Kelp Bowl can go in the oven. The answer sits a few lines down that very page, yet they'd rather ask than scroll, so the question reaches your inbox the next morning.

That shop is Tidewell Ceramics, our fictional pottery brand, rebuilt for this guide as a small React app whose two pages, Shop and Care guide, swap without a page reload. Its agent, Pearl, had already read Tidewell's website in AssistLoop. Every screen below comes from that app.

The minute-and-a-half video shows the whole install in that app, from the dashboard to the second page.

What you need

  • A trained AssistLoop agent. No account yet? Sign up, click Create Agent, name it, type your address under Website/Links and click Fetch Links. The final Create Agent click starts training on your website.

  • Your React project open in an editor, with its dev server running so you can test before you deploy.

  • The file that wraps every page: App.jsx in a Vite or Create React App project, the root layout in Next.js.

Where the code goes in your app

A React app renders every page from one main component, so that component is the place for the chat: it loads once and nothing unmounts it when the visitor changes page. React's own guide to effects names this kind of job. It says: "Effects let you run some code after rendering so that you can synchronize your component with some system outside of React." Its list of outside systems includes third-party widgets (Synchronizing with Effects, react.dev).

Your appPut it inLoad with
Vite or CRAApp.jsxuseEffect
Next.jsRoot layoutScript
Plain HTMLEach pageScript tag

In Stack Overflow's 2025 Developer Survey, 44.7% of the 23,678 people who answered the web-framework question had done extensive development work in React over the past year.

Not on React? A hand-coded site takes the HTML code before the closing body tag, and WordPress has an official plugin. For Vue, the dashboard says to use the mounted lifecycle hook instead of useEffect; we didn't build a Vue app for this guide.

Three cards joined by dashed lines: AssistLoop's React / Vue / SPA tab saying to use a useEffect hook in your main component, the useEffect hook in App.jsx, and the agent's answer in the chat on the app's Shop page.

The React tab's advice, the hook that follows it, and the chat it puts on the page.

Why the hook checks for the script first

Under StrictMode, React runs a component's effects twice in development, on purpose: React's docs explain that it mounts each component twice to stress-test its effects, and only in development. Vite and Create React App projects wrap the app in StrictMode from the start.

We removed the check to see what happens. With the dev server running, the page got two copies of the AssistLoop script tag and two chat widgets. With the check back in, it had one of each. The fix is the first line of the hook: give the script tag an id, and return early when an element with that id is already on the page.

Step 1: Pick React / Vue / SPA in Widget Installation

Open your agent in AssistLoop, choose Widget Installation in the sidebar and pick React / Vue / SPA. The tab has three steps: extract the script logic, integrate it with your framework, then deploy and verify. For React, it says to use a useEffect hook in your main component.

AssistLoop's Widget Installation page with three numbered highlights: 1, the React / Vue / SPA tab; 2, the framework step, which says to use a useEffect hook in your main component for React; 3, the agent ID line inside the dark Installation Code card.

The Installation Code card shows the same snippet as the HTML tab. You only need two things from it: the script address and your agent ID.

The agent ID is the long value after agentId in the code card. Settings → General shows it too, as Agent ID with a copy icon beside it.

Step 2: Add one useEffect to App.jsx

Open src/App.jsx, add useEffect to the React import, then put the hook at the top of your App component, above the return:

import { useEffect } from 'react' // or add useEffect to your existing React import

export default function App() {
  useEffect(() => {
    if (document.getElementById('assistloop')) return
    const script = document.createElement('script')
    script.id = 'assistloop'
    script.src = 'https://assistloop.ai/assistloop-widget.js'
    script.onload = () =>
      window.AssistLoopWidget.init({ agentId: 'YOUR_AGENT_ID' })
    document.head.appendChild(script)
  }, [])

  // ...the rest of your component, unchanged
}

Replace YOUR_AGENT_ID with the ID from Step 1. The empty array at the end tells React to run the hook once, when the component first appears, and the id check covers the extra development run.

The code editor showing App.jsx with three numbered highlights: 1, useEffect added to the React import on line 1; 2, the line that returns early when the assistloop script is already on the page; 3, the onload line that starts the widget with the agent ID.

Lines 8 to 16 are the whole install. Everything below the hook is the app as it was.

Writing TypeScript? TypeScript doesn't know about window.AssistLoopWidget, so your editor and tsc report "Property 'AssistLoopWidget' does not exist on type 'Window & typeof globalThis'". Declare it once, at the top of the file, or in a .d.ts file that ends with export {}:

declare global {
  interface Window {
    AssistLoopWidget: { init: (options: { agentId: string }) => void }
  }
}

Step 3: Save, then ask a question in your app

Save the file. The dev server reloads the app, and the chat button appears in the bottom-right corner. Open it and ask something your site answers.

On the Care guide page we asked "Can the Kelp Bowl go in the oven?" Pearl first asked for a name and an email, because its pre-chat form is switched on (a third question, the order number, is optional, and we skipped it). Then it answered:

Hi Ravi, yes—the Kelp Bowl is oven safe up to 400°F. Avoid sudden temperature changes, such as moving it straight from the fridge into a hot oven.

The React app's Care guide page with the chat open and three numbered highlights: 1, the app's menu on the Care guide page; 2, the question about putting the Kelp Bowl in the oven; 3, the agent's short pre-chat form asking for a name and then an email.

The Care guide's own oven line says the same thing in fewer words, and both facts in the reply come from it.

The Care guide's In the oven line beside the agent's reply, with the same two facts ringed in each: oven safe up to 400 degrees Fahrenheit, and avoid sudden temperature changes.

Want your React app answering questions like this?
One hook and your agent ID. Free plan, no card.

Add it to my app →

Step 4: Change pages and check the chat stays

Click to another page of your app. Because the hook lives in the component that wraps every page, nothing removes the widget, and the open conversation carries over. In our app, Ravi's form answers and Pearl's reply were still in the chat on the Shop page.

The React app's Shop page with the chat still open and two numbered highlights: 1, the app's menu, with Shop now underlined; 2, the same conversation, with the form answers and the oven reply.

Our test app swaps pages without a reload. We checked a router too: in the Next.js app, the same chat widget was still on the page after a link click.

When it works on the dev server, deploy the app the way you usually do and open the live site once in a private window.

Adding the chat in Next.js

The React tab's line for Next.js is to use the framework's script component. Next.js documents a strategy for exactly this kind of script: lazyOnload loads it when the browser is idle, and the examples listed for it include "Chat support plugins" (Script Component, Next.js docs, updated August 2026).

Put the script in a small client component, because onLoad works only in Client Components:

// app/AssistLoop.js
'use client'

import Script from 'next/script'

export default function AssistLoop() {
  return (
    <Script
      id="assistloop"
      src="https://assistloop.ai/assistloop-widget.js"
      strategy="lazyOnload"
      onLoad={() => window.AssistLoopWidget.init({ agentId: 'YOUR_AGENT_ID' })}
    />
  )
}

Then render it once in the root layout, inside the body and after children:

// app/layout.js
import AssistLoop from './AssistLoop'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <AssistLoop />
      </body>
    </html>
  )
}

In our Next.js 15.5 test app, the script loaded once, and after a link click from the home page to a second page the same chat widget was still there.

If the chat doesn't appear, or appears twice

Start in the browser's developer tools (F12): the Console shows script errors, and the Elements panel shows how many AssistLoop script tags the page has.

  • Two chat buttons. The hook ran twice without the id check, the component is mounted in more than one place, or the HTML snippet is also in index.html. Keep the id line and one install only.

  • No chat button and no error. The hook is in a component that isn't on screen yet, such as one page. Move it to App.jsx or the root layout.

  • "Cannot read properties of undefined (reading 'init')" (Chrome's wording). Something called init before the script finished loading. Call it from onload (or onLoad in Next.js), as in the code above.

  • "document is not defined" during a server render. The code runs outside the hook. Touch document only inside useEffect, or use the Script component above; 'use client' alone doesn't stop the server render.

  • The code still says YOUR_AGENT_ID. Paste your own ID from Widget Installation or Settings → General.

  • An ad blocker or a Content Security Policy blocks it. Try a private window with extensions off. If your app sends a Content Security Policy, the Console names the directive that blocked assistloop.ai. Add assistloop.ai there: script-src for the script, frame-src for the chat window.

How we tested this

  • The app: Tidewell's shop rebuilt as a React app on Vite, with Shop and Care guide pages that the menu swaps without a reload, wrapped in StrictMode. The recording used VS Code for the web (code-server) at 127.0.0.1:8188 and the Vite dev server at localhost:5173, the addresses in the step figures.

  • The agent: Pearl, trained on Tidewell's published website, with its pre-chat form switched on. The answer in Step 3 is quoted word for word.

  • The recheck, 23 October 2026: a fresh copy of the app on React 19.3 and Vite 6.4, run with and without the id check, counting script tags and widget frames after load and after a page change. Then a new Next.js 15.5 App Router app with the client component above and two routes linked with next/link.

  • TypeScript: the hook compiled in strict mode with the declaration above, and failed with the quoted error without it.

  • Not tested: Vue, Remix, Gatsby, React Router and production builds on a host. The dashboard's Vue step is quoted, not tried.

  • The screenshots: frames from the October recording, cropped, with pins and outlines drawn around the interface, never on it.

Frequently asked questions

Where should the code go in a React app?

In the component that wraps every page, usually App.jsx, or the root layout in Next.js. A hook inside a single page would only load the chat when that page opens.

Will StrictMode load the chat twice?

Only without the id check. StrictMode runs effects twice in development, and in our test that put two widgets on the page. The check stops the second run, and production builds run the effect once anyway.

Is there an npm package for the AssistLoop widget?

Widget Installation offers a script, not a package. The hook in Step 2 loads that script, so there's nothing to install and no version to keep updated.

Can I paste the HTML code into index.html instead?

In a Vite or Create React App project, yes: index.html is a plain page shell, and the HTML guide shows where the code goes. The hook keeps the install in your React code, where you can load the chat only after cookie consent or only for signed-in users. Next.js has no index.html, so use the Script component there.

Will the chat slow down my app's first load?

It doesn't hold up your first render. An effect runs after React has rendered, and the script loads in the background from there. In Next.js, lazyOnload waits for browser idle time. We didn't time page loads for this guide.

Back in that React shop, the oven question gets its answer on the Care guide itself, and the conversation follows the customer to the Shop page. Put an agent that knows your site into your React app, and when it gets an answer wrong, here's how to teach it the right one with a Q&A pair.

AssistLoop logo, an indigo speech bubble

Written by AssistLoop Team
Updated October 2026

We build AssistLoop, an AI support agent that answers customers from your own website and documents, and hands the chat to your team when a person should step in.

★★★★★ "I also love that it's flexible enough to be used with different websites, and it's brandable, allowing for customization." Ryan N., Managing Owner, Working Nomads, on G2

Build Custom AI Chatbot for Your Business FREE

Create intelligent AI agents that handle customer support, boost conversions, and work 24/7 for your business.

Build Your Agent
All articles
  • Chatbot Containment Rate: Meaning and Measurement

    Chatbot containment rate measures the share of conversations that stay automated without human transfer. This guide shows how to calculate it, document reporting rules, and pair it with handoff, repeat-contact, CSAT, and resolution-quality checks.

  • Chatbot Human Handoff: What Buyers Should Check

    Chatbot human handoff works only when the customer reaches the right person with the right context. This guide gives buyers a four-stage test for triggers, routing, context transfer, and agent follow-up.

Get started

Build your own
support agent.

Free plan, no credit card. Train it on your files, website, text and Q&A, then see how it answers.