tjtanjin / react-chatbotify

A modern React library for creating flexible and extensible chatbots.
https://react-chatbotify.com
MIT License
200 stars 107 forks source link

[Bug] NextJs Window not defined issue #32

Closed kavin-bytive closed 6 months ago

kavin-bytive commented 6 months ago

Bug Description

./app/globals.css
 ⚠ Fast Refresh had to perform a full reload due to a runtime error.
 ⚠ Fast Refresh had to perform a full reload due to a runtime error.
 ⨯ node_modules/react-chatbotify/dist/index.js (915:8) @ window
 ⨯ ReferenceError: window is not defined
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
    at eval (./app/component/atom/Banner.js:13:74)
    at (ssr)/./app/component/atom/Banner.js (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/app/page.js:283:1)
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
null
 ⨯ node_modules/react-chatbotify/dist/index.js (915:8) @ window
 ⨯ ReferenceError: window is not defined
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
    at eval (./app/component/atom/Banner.js:13:74)
    at (ssr)/./app/component/atom/Banner.js (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/app/page.js:283:1)
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
null
 ⨯ node_modules/react-chatbotify/dist/index.js (915:8) @ window
 ⨯ ReferenceError: window is not defined
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
    at eval (./app/component/atom/Banner.js:13:74)
    at (ssr)/./app/component/atom/Banner.js (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/app/page.js:283:1)
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
null
 ⨯ node_modules/react-chatbotify/dist/index.js (915:8) @ window
 ⨯ ReferenceError: window is not defined
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
    at eval (./app/component/atom/Banner.js:13:74)
    at (ssr)/./app/component/atom/Banner.js (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/app/page.js:283:1)
    at __webpack_require__ (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:33:42)
null

Code Snippet

"use client";
"use strict";
import { lazy, Suspense, useContext, useEffect, useState } from "react";
import ChatBot, { getDefaultBotOptions } from "react-chatbotify";
// import { useAiState } from "../atom/AistateProvier";
import { BotOptionsContext } from "react-chatbotify";
import { useRouter } from "next/navigation";
import { AiStateContext } from "../atom/AistateProvier";

const MyChatBot = () => {
  const [form, setForm] = useState({});
  // const { isOpen, setIsOpen } = useAiState();
  const { isOpen, setIsOpen } = useContext(AiStateContext);

  const defaultBotOptions = getDefaultBotOptions();
  const router = useRouter();
  const [option, setOption] = useState({
    ...defaultBotOptions,
    theme: {
      ...defaultBotOptions.theme,
      embedded: false,
      secondaryColor: "#1A3057",
      primaryColor: "#077D65",
    },
    header: {
      ...defaultBotOptions.header,
      title: <h3>AI Assitant!!</h3>,
      avatar: "https://img.icons8.com/color/48/message-bot.png",
    },
    chatHistory: {
      ...defaultBotOptions.chatHistory,
      storageKey: "example_advanced_form",
    },
    chatButton: {
      ...defaultBotOptions.chatButton,
      icon: "https://img.icons8.com/color/48/message-bot.png",
    },
    tooltip: {
      ...defaultBotOptions.tooltip,
      mode: "CLOSE",
      text: "AI Assistant! 😊",
    },
    isOpen: isOpen,
    advance: {
      useCustomBotOptions: true,
    },
    footer: {
      ...defaultBotOptions.footer,
      text: (
        <div style={{ cursor: "pointer" }} onClick={() => router.push("/")}>
          <span>Powered By </span>
          <span style={{ fontWeight: "bold" }}>
            <span> MediXplore.co</span>
          </span>
        </div>
      ),
    },
  });
  useEffect(() => {
    setOption((prev) => ({ ...prev, isOpen }));
  }, [isOpen]);

  console.log("&&&&&&&", isOpen);
  const fetchMinMax = async () => {
    let countryURL = ``;
    for (let index in form.countries) {
      countryURL += `&filters[$or][${index}][hospital_form][country][id][$eq]=${form.countries[index]}`;
    }
    const maxURL = `https://cms-backend.medixplore.co/api/hospitaltreatmentprices?sort=max:desc&pagination[page]=1&pagination[pageSize]=1&filters[treatment][id][$eq]=${form.treatment}${countryURL}`;
    const getMax = fetch(maxURL);
    const minURL = `
        https://cms-backend.medixplore.co/api/hospitaltreatmentprices?sort=min&pagination[page]=1&pagination[pageSize]=1&filters[treatment][id][$eq]=${form.treatment}${countryURL}`;
    const getMin = fetch(minURL);

    const [min, max] = await Promise.allSettled([getMin, getMax]);
    const minData = await min.value.json();
    const maxData = await max.value.json();
    return {
      min: minData.data[0].attributes.min,
      max: maxData.data[0].attributes.max,
    };
  };

  const flow = {
    start: {
      message:
        "I'm Amy from MediXplore. We help people find the best medical treatments, whether you're looking for options close to home or considering treatment abroad. ",
      transition: { duration: 1000 },
      path: "init",
    },
    init: {
      message: "Here is list of most popular treatments.",
      options: ["Knee replacement", "Heart Bypass surgery", "Spinal Fusion"],
      chatDisabled: true,
      function: (params) => {
        // to be replaced via api calls l
        const idMappings = {
          "Knee replacement": 3,
          "Heart Bypass surgery": 56,
          "Spinal Fusion": 51,
        };

        const val = idMappings[params.userInput];

        console.log("val is", val);
        setForm({ ...form, treatment: val });
      },
      path: "ask_countries",
    },
    ask_countries: {
      message:
        "Great! To help narrow down your options, tell me, in which country are you generally interested in receiving treatment? We have several destinations known for their quality and affordability. Some top destinations include:",
      checkboxes: {
        items: ["Singapore", "Malaysia", "Thailand"],
        min: 1,
        max: 3,
      },
      chatDisabled: false,
      function: (params) => {
        const countriesMapping = {
          Singapore: 1,
          Malaysia: 2,
          Thailand: 4,
        };
        let selections = [];

        for (let country of params.userInput.split(",")) {
          if (countriesMapping[country.trim()]) {
            selections.push(countriesMapping[country.trim()]);
          }
        }
        setForm({ ...form, countries: selections });
        console.log("selections", selections);
      },
      path: "ask_budget",
    },
    ask_budget: {
      message: async (params) => {
        const val = await fetchMinMax();
        console.log("val", val);
        setForm({ ...form, range: val });
        return `Wonderful! To give you the most relevant options, it helps to know your budget for the procedure. Minimum: ${val.min}$ Maximum : ${val.max}$`;
      },

      function: (params) => setForm({ ...form, budget: params.userInput }),
      path: async (params) => {
        console.log("val is", form.range);
        if (isNaN(Number(params.userInput))) {
          await params.injectMessage("Budget needs to be a number!");
          return;
        }
        if (params.userInput < form.range.min) {
          await params.injectMessage(
            `Budget can't be less then specified range i.e. Minimum: ${form.range.min}$ Maximum : ${form.range.max}$`
          );
          return;
        }
        return "input_name";
      },
    },

    input_name: {
      message: "Kindly enter your name",
      chatDisabled: false,
      function: (params) => setForm({ ...form, name: params.userInput }),
      path: "input_email",
    },
    input_email: {
      message: "Enter your email",
      chatDisabled: false,
      function: (params) => setForm({ ...form, email: params.userInput }),
      path: "input_phone",
    },
    input_phone: {
      message: "Enter your phone",
      chatDisabled: false,
      function: (params) => setForm({ ...form, phone: params.userInput }),
      path: "prepare_search",
    },
    prepare_search: {
      message:
        "Yipee!! Your search results are ready!! Kindly click below to view them!!",
      options: ["See Search Results!!", "Retry"],
      chatDisabled: true,
      path: "process_input",
    },
    process_input: {
      transition: { duration: 0 },
      chatDisabled: true,
      path: async (params) => {
        switch (params.userInput) {
          case "See Search Results!!":
            let link = `/search?budget=${form.budget}&tr=${form.treatment}`;

            for (let country of form.countries) {
              link += `&co=${country}`;
            }
            await params.injectMessage("Sit tight! I'll send you right there!");
            setTimeout(() => {
              router.push(link);
            }, 1000);
            return "repeat";
          case "Retry":
            return "repeat";
            break;
        }
      },
    },
    repeat: {
      transition: { duration: 1000 },
      path: "start",
    },
  };
  const [isLoaded, setIsLoaded] = useState(false);
  useEffect(() => {
    setIsLoaded(true);
  }, []);
  return (
    <BotOptionsContext.Provider
      value={{ botOptions: option, setBotOptions: setOption }}
    >
      {isLoaded && (
        <Suspense fallback={<div>Loading...</div>}>
          <ChatBot flow={flow} options={option} />
        </Suspense>
      )}
    </BotOptionsContext.Provider>
  );
};

export default MyChatBot;

as per given solution in this issue https://github.com/tjtanjin/react-chatbotify/issues/19 it works to import the ChatBot but i am also using getDefaultBotOptions and BotOptionsContext and they are not working while trying with lazy function.

kavin-bytive commented 6 months ago

Hi @tjtanjin , i tried importing all 3 of them like this but didn't worked

`// Lazy load the components from react-chatbotify const ChatBot = lazy(() => import('react-chatbotify').then(mod => mod.ChatBot));

const getDefaultBotOptions = lazy(() => import('react-chatbotify').then(mod => mod.getDefaultBotOptions));

const BotOptionsContext = lazy(() => import('react-chatbotify').then(mod => mod.BotOptionsContext)); `

tjtanjin commented 6 months ago

@kavin-bytive Ok so it's a little hard to read with the formatting above 😰

From what I can make off the stuffs above, you can try the following:

kavin-bytive commented 6 months ago
const ChatBot = lazy(() => import("react-chatbotify"));
const {BotOptionsContext} = lazy(() => import("react-chatbotify"));
  <>
    {isLoaded &&
  (  <BotOptionsContext.Provider
      value={{ botOptions: option, setBotOptions: setOption }}
    >

        <Suspense fallback={<div>Loading...</div>}>
          <ChatBot flow={flow} options={option} />
        </Suspense>

    </BotOptionsContext.Provider>)
}
    </>

error on loading

Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'Provider')

Source
app/component/molecules/MyChatbot.js (225:24) @ Provider

  223 |   <>
  224 |   {isLoaded && 
> 225 | (  <BotOptionsContext.Provider
      |                      ^
  226 |     value={{ botOptions: option, setBotOptions: setOption }}
  227 |   >

228 |

kavin-bytive commented 6 months ago

@kavin-bytive Ok so it's a little hard to read with the formatting above 😰

From what I can make off the stuffs above, you can try the following:

  • Call getDefaultBotOptions() within your useEffect hook
  • Shift your BotOptionsProvider into where the <ChatBot/> is being lazily loaded as well

sorry for bad formatting, i have edited it :P

tjtanjin commented 6 months ago

@kavin-bytive Give this a try:

"use client";
import { lazy, Suspense, useEffect, useState } from "react"
import { Options } from "react-chatbotify";

export default function Home() {
  const [option, setOption] = useState<Options>();
  const [BotOptionsContext, setBotOptionsContext] = useState<any>(null);

  const ChatBot = lazy(() => import("react-chatbotify"));
  const [isLoaded, setIsLoaded] = useState(false);
  useEffect(() => {
    const loadBotOptions = async () => {
      // note: do imports here
      const { getDefaultBotOptions, BotOptionsContext } = await import("react-chatbotify");

      // note: set bot options and context here
      setBotOptionsContext(BotOptionsContext);
      const defaultBotOptions = getDefaultBotOptions();
      setOption({
        ...defaultBotOptions,
        // note: your other styles here
      });
    };

    loadBotOptions();
    setIsLoaded(true);
  }, [])

  return (
    <>
    {isLoaded && (
      <Suspense fallback={<div>Loading...</div>}>
        {BotOptionsContext != null && (
          <BotOptionsContext.Provider
            value={{ botOptions: option, setBotOptions: setOption }}
          >
            <ChatBot />
          </BotOptionsContext.Provider>)
        }
      </Suspense>
    )}
    </>
  );
}
kavin-bytive commented 6 months ago
"use client";
"use strict";
import { lazy, Suspense, useContext, useEffect, useState } from "react";

// import { useAiState } from "../atom/AistateProvier";
import { useRouter } from "next/navigation";
import { AiStateContext } from "../atom/AistateProvier";

const MyChatBot = () => {
  const [form, setForm] = useState({});
  const [option, setOption] = useState(null);
  const [BotOptionsContext, setBotOptionsContext] = useState(null);

  const ChatBot = lazy(() => import("react-chatbotify"));

  // const { isOpen, setIsOpen } = useAiState();
  const { isOpen, setIsOpen } = useContext(AiStateContext);

  const router = useRouter();

  useEffect(() => {
    const loadBotOptions = async () => {
      // note: do imports here
      const { getDefaultBotOptions, BotOptionsContext } = await import("react-chatbotify");

      // note: set bot options and context here
      setBotOptionsContext(BotOptionsContext);
      const defaultBotOptions = getDefaultBotOptions();
      console.log("🚀 ~ loadBotOptions ~ defaultBotOptions:", defaultBotOptions)
      setOption({
        ...defaultBotOptions,
        // note: your other styles here
        theme: {
          ...defaultBotOptions.theme,
          embedded: false,
          secondaryColor: "#1A3057",
          primaryColor: "#077D65",
        },
        header: {
          ...defaultBotOptions.header,
          title: <h3>AI Assistant!!</h3>,
          avatar: "https://img.icons8.com/color/48/message-bot.png",
        },
        chatHistory: {
          ...defaultBotOptions.chatHistory,
          storageKey: "example_advanced_form",
        },
        chatButton: {
          ...defaultBotOptions.chatButton,
          icon: "https://img.icons8.com/color/48/message-bot.png",
        },
        tooltip: {
          ...defaultBotOptions.tooltip,
          mode: "CLOSE",
          text: "AI Assistant! 😊",
        },
        isOpen: isOpen,
        advance: {
          useCustomBotOptions: true,
        },
        footer: {
          ...defaultBotOptions.footer,
          text: (
            <div style={{ cursor: "pointer" }} onClick={() => router.push("/")}>
              <span>Powered By </span>
              <span style={{ fontWeight: "bold" }}>
                <span> MediXplore.co</span>
              </span>
            </div>
          ),
        },
      });
    };

    loadBotOptions();
    setIsLoaded(true);
  }, [])

  useEffect(() => {
    setOption((prev) => ({ ...prev, isOpen }));
  }, [isOpen]);

  console.log("&&&&&&&", isOpen);
  const fetchMinMax = async () => {
    let countryURL = ``;
    for (let index in form.countries) {
      countryURL += `&filters[$or][${index}][hospital_form][country][id][$eq]=${form.countries[index]}`;
    }
    const maxURL = `https://cms-backend.medixplore.co/api/hospitaltreatmentprices?sort=max:desc&pagination[page]=1&pagination[pageSize]=1&filters[treatment][id][$eq]=${form.treatment}${countryURL}`;
    const getMax = fetch(maxURL);
    const minURL = `
        https://cms-backend.medixplore.co/api/hospitaltreatmentprices?sort=min&pagination[page]=1&pagination[pageSize]=1&filters[treatment][id][$eq]=${form.treatment}${countryURL}`;
    const getMin = fetch(minURL);

    const [min, max] = await Promise.allSettled([getMin, getMax]);
    const minData = await min.value.json();
    const maxData = await max.value.json();
    return {
      min: minData.data[0].attributes.min,
      max: maxData.data[0].attributes.max,
    };
  };

  const flow = {
    start: {
      message:
        "I'm Amy from MediXplore. We help people find the best medical treatments, whether you're looking for options close to home or considering treatment abroad. ",
      transition: { duration: 1000 },
      path: "init",
    },
    init: {
      message: "Here is list of most popular treatments.",
      options: ["Knee replacement", "Heart Bypass surgery", "Spinal Fusion"],
      chatDisabled: true,
      function: (params) => {
        // to be replaced via api calls l
        const idMappings = {
          "Knee replacement": 3,
          "Heart Bypass surgery": 56,
          "Spinal Fusion": 51,
        };

        const val = idMappings[params.userInput];

        console.log("val is", val);
        setForm({ ...form, treatment: val });
      },
      path: "ask_countries",
    },
    ask_countries: {
      message:
        "Great! To help narrow down your options, tell me, in which country are you generally interested in receiving treatment? We have several destinations known for their quality and affordability. Some top destinations include:",
      checkboxes: {
        items: ["Singapore", "Malaysia", "Thailand"],
        min: 1,
        max: 3,
      },
      chatDisabled: false,
      function: (params) => {
        const countriesMapping = {
          Singapore: 1,
          Malaysia: 2,
          Thailand: 4,
        };
        let selections = [];

        for (let country of params.userInput.split(",")) {
          if (countriesMapping[country.trim()]) {
            selections.push(countriesMapping[country.trim()]);
          }
        }
        setForm({ ...form, countries: selections });
        console.log("selections", selections);
      },
      path: "ask_budget",
    },
    ask_budget: {
      message: async (params) => {
        const val = await fetchMinMax();
        console.log("val", val);
        setForm({ ...form, range: val });
        return `Wonderful! To give you the most relevant options, it helps to know your budget for the procedure. Minimum: ${val.min}$ Maximum : ${val.max}$`;
      },

      function: (params) => setForm({ ...form, budget: params.userInput }),
      path: async (params) => {
        console.log("val is", form.range);
        if (isNaN(Number(params.userInput))) {
          await params.injectMessage("Budget needs to be a number!");
          return;
        }
        if (params.userInput < form.range.min) {
          await params.injectMessage(
            `Budget can't be less then specified range i.e. Minimum: ${form.range.min}$ Maximum : ${form.range.max}$`
          );
          return;
        }
        return "input_name";
      },
    },

    input_name: {
      message: "Kindly enter your name",
      chatDisabled: false,
      function: (params) => setForm({ ...form, name: params.userInput }),
      path: "input_email",
    },
    input_email: {
      message: "Enter your email",
      chatDisabled: false,
      function: (params) => setForm({ ...form, email: params.userInput }),
      path: "input_phone",
    },
    input_phone: {
      message: "Enter your phone number",
      chatDisabled: false,
      function: (params) => setForm({ ...form, phone: params.userInput }),
      path: "prepare_search",
    },
    prepare_search: {
      message:
        "Yipee!! Your search results are ready!! Kindly click below to view them!!",
      options: ["See Search Results!!", "Retry"],
      chatDisabled: true,
      path: "process_input",
    },
    process_input: {
      transition: { duration: 0 },
      chatDisabled: true,
      path: async (params) => {
        switch (params.userInput) {
          case "See Search Results!!":
            let link = `/search?budget=${form.budget}&tr=${form.treatment}`;

            for (let country of form.countries) {
              link += `&co=${country}`;
            }
            await params.injectMessage("Sit tight! I'll send you right there!");
            setTimeout(() => {
              router.push(link);
              setIsOpen(false)
            }, 1000);
            return "repeat";
          case "Retry":
            return "repeat";
            break;
        }
      },
    },
    repeat: {
      transition: { duration: 1000 },
      path: "start",
    },
  };
  const [isLoaded, setIsLoaded] = useState(false);
  // useEffect(() => {
  //   setIsLoaded(true);
  // }, []);
  return (
    <>
    {isLoaded && (
      <Suspense fallback={<div>Loading...</div>}>
        {BotOptionsContext != null && (
          <BotOptionsContext.Provider
            value={{ botOptions: option, setBotOptions: setOption }}
          >
            <ChatBot flow={flow} options={option}/>
          </BotOptionsContext.Provider>)
        }
      </Suspense>
    )}
    </>
  );
};

export default MyChatBot;

tried this, but still getting same error @tjtanjin

kavin-bytive commented 6 months ago
ReferenceError: window is not defined
    at 90180 (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/chunks/180.js:2:5739)
    at t (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:1:127)
    at 72098 (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/app/page.js:1:3287)
    at t (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:1:127)
    at F (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:94693)
    at j (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:93244)
    at rP (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:33905)
    at nN (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:62423)
    at nB (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:67657)
    at nF (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:66824)

Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error

ReferenceError: window is not defined
    at 90180 (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/chunks/180.js:2:5739)
    at t (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:1:127)
    at 72098 (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/app/page.js:1:3287)
    at t (/Users/kavinsharma/Desktop/work/MediExplore/.next/server/webpack-runtime.js:1:127)
    at F (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:94693)
    at j (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:93244)
    at rP (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:33905)
    at nN (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:62423)
    at nB (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:67657)
    at nF (/Users/kavinsharma/Desktop/work/MediExplore/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:66824)
kavin-bytive commented 6 months ago

@tjtanjin finally it worked, above codebase i have shared is working fine by moving things to use-effect https://github.com/tjtanjin/react-chatbotify/issues/32#issuecomment-2054187127, i missed to remove the import from another file, due to which it was not working.

thanks again for quick response and creating this amazing package.