javascriptreactjsbotframeworkdirect-line-botframework

How can I get response from the server without creating directline object everytime in Microsoft Bot Framework Direct Line JS Client (REACTJS)


I am passing getResponse() function in the useEffect with userInput as dependency array. Everytime a user sends input this function is triggered and I get new directline object created everytime. Problem I am facing is that I am creating bot everytime I send a request to bot. How can I create an object only once on initial render and then use the object to connect with bot.

Please help!

Here's the botframework documentation I am following

Here's the code I wrote to communicate with bot in ReactJS.

import React, { useEffect, useState } from "react";
import "./App.css"
import { DirectLine } from "botframework-directlinejs";
import { browserName, browserVersion, osName } from "react-device-detect";
import { ConnectionStatus } from 'botframework-directlinejs';
import Header from "./Components/Header";
import MainBody from "./Components/MainBody";
import BottomWrapper from "./Components/BottomWrapper";

function App() {
  useEffect(() => {
    const userIpDetails = fetchIp();
    setUserIp(userIpDetails);
  }, []);

  // to fetch user IP
  const fetchIp = async () => {
    const response = await fetch("https://api.ipify.org/?format=json");
    const ip = await response.json();
    const userIP = ip.ip;
    return userIP;
  };

  const [loaded, setLoaded] = useState(false);
  const [message, setMessage] = useState("");
  const [userInput, setUserInput] = useState([]);
  const [messages, setMessages] = useState([]);
  const [userIp, setUserIp] = useState("");

  var client;

  useEffect(() => {
    setLoaded(true);
    getResponse();
  }, [userInput]);

  // to get DirectLine Streaming Token
  const getDirectLineStreamingToken = async function () {
    const res = await fetch(
      https://directline.botframework.com/v3/directline/tokens/generate,
      {
        method: "POST",
        headers: {
          Authorization:
            `Bearer TOKEN`,
        },
      }
    );
    const { token } = await res.json();
    return token;
  };

  // send request via message box
  const sendRequest = () => {
    console.log("request sent");
    client
      ?.postActivity({
        from: {
          id: "my_id",
          name: "Software Company",
          avatarUrl:
            "https://demos.telerik.com/kendo-ui/content/chat/VacationBot.png",
        },
        type: "message",
        text: message,
        channelData: {
          iP: userIp,
          platform: `${osName}`,
          Browser: `${browserName} ${browserVersion}`,
        },
      })
      ?.subscribe(
        (id) => console.log("Posted activity, assigned ID ", id),
        (error) => console.log("Error posting activity", error)
      );
  };

  // receive response from server
  const getResponse = function () {
    getDirectLineStreamingToken().then(function (token) {
      client = new DirectLine({
        domain: "https://directline.botframework.com/v3/directline",
        token: token,
      });

      client.activity$.subscribe((activity) => {
        onResponse(activity);
      });

      client.connectionStatus$
      .subscribe(connectionStatus => {
          switch(connectionStatus) {
              case ConnectionStatus.Uninitialized:    // the status when the DirectLine object is first created/constructed
              case ConnectionStatus.Connecting:       // currently trying to connect to the conversation
              case ConnectionStatus.Online:           // successfully connected to the converstaion. Connection is healthy so far as we know.
              case ConnectionStatus.ExpiredToken:     // last operation errored out with an expired token. Your app should supply a new one.
              case ConnectionStatus.FailedToConnect:  // the initial attempt to connect to the conversation failed. No recovery possible.
              case ConnectionStatus.Ended:            // the bot ended the conversation
          }
          console.log("connection status ", connectionStatus)
      });
    })
    .catch((e) => console.log("bot error", e));;
  };

  // handle response received from server
  const onResponse = function (activity) {
    console.log("activity is ", activity);
    let receivedResponse = activity.text;
    if (activity.inputHint === "acceptingInput") {
      setMessages([
        ...messages,
        { messageKey: receivedResponse, isbotmessage: true },
      ]);
    }
  };

  const handleChange = function (event) {
    setMessage(event.target.value);
    console.log(event.target.value);
  };

  const handleKeyPress = function (event) {
    if (event.key === "Enter") {
      sendRequest();
      setMessages([...messages, { messageKey: message, isbotmessage: false }]);
      setUserInput([
        ...messages,
        { messageKey: userInput, isbotmessage: false },
      ]);
    }
  };

  const handleClick = function (event) {
    sendRequest();
    setMessages([...messages, { messageKey: message, isbotmessage: false }]);
    setUserInput([...messages, { messageKey: userInput, isbotmessage: false }]);
  };

  return loaded ? (
    <div className="App">
      <div className="chat-window">
        <Header />
        <MainBody messages={messages}/>
        <BottomWrapper message={message} handleChange={handleChange} handleKeyPress={handleKeyPress} handleClick={handleClick}/>
      </div>
    </div>
  ) : (
    <p>Loading...</p>
  );
}

export default App;


Solution

  • You should move the creation of the DirectLine client object, as well as subscribing to activity and connectionStatus, out of the function or configure the useEffect() hook to run only once. The client object should be created once and then referenced, as needed. Check out this SO post that discusses how to implement these options.

    Additionally, because you already subscribe to activity and connectionStatus, these should be allowed to run independently so they can function as expected. When the connectionStatus changes, the switch statement will detect this. As the different cases are met, you could assign the status thru useState() and detect the changes to state thru the useEffect() hook.