Search code examples
reactjsbotframeworkreact-selectweb-chat

Auto-completion : How to replace the input field of the Webchat with your own react-select element


I have a React app and I want to replace the input bar of the Webchat with my own select element (I'm using react-select).

Here is the select element under the webchat: Imgur

return (
    <div className="WebChat" >
      <ReactWebChat //WebChat
        className={ `${ className || '' } web-chat` }
        directLine={ this.createDirectLine(token) }
        store={ store }
        styleSet={ styleSet } />
      <Select //my select element
        autoFocus="true"
        className="basic-single"
        classNamePrefix="select"
        defaultValue={'default'}
        isClearable={isClearable}
        isSearchable={isSearchable}
        name="Questions"
        options={groupedQuestions}
        closeMenuOnScroll= "true"
        placeholder="Example"
      />
     </div>
);

edit Thanks to @tdurnford, here is my implementation:

WebChat.js

import React from 'react'
import { createStore } from 'botframework-webchat'
import WebChatReact from './WebChatReact'
+import Searchbox from "./ImprovedSendBox"
+import setSendBox from "botframework-webchat-core/lib/actions/setSendBox";
+import submitSendBox from "botframework-webchat-core/lib/actions/submitSendBox";

import './WebChat.css'

export default class extends React.Component {
  constructor(props) {
    super(props);

    this.handleFetchToken = this.handleFetchToken.bind(this);

    const store = createStore({}, ({ dispatch }) => next => action => {
      if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
        dispatch({
         type: 'WEB_CHAT/SEND_EVENT',
         payload: {
           name: 'webchat/join',
           value: { }
         }
        });
        setTimeout(() => {
          dispatch({
            type: 'WEB_CHAT/SEND_MESSAGE',
            payload: { text:'Démarrer' }
          }
          );
        }, 1000);
      }
+      if (action.type === 'WEB_CHAT/SET_SEND_BOX') {
+       this.setState({
+          searchValue: action.payload.text,
+        })
+      }
      return next(action);
    });

    this.state = {
      store,
      token: null,
+      searchValue: "",
+      searchSelection: "",
    };
  }

+  handleSearchInput = (e, { action }, store) => {
+    if (
+      action === "menu-close" ||
+      action === "input-blur" ||
+      action === "set-value"
+    ) {
+      return;
+    } else {
+      this.setState({ searchValue: e });
+    }
+    store.dispatch(setSendBox(e));
+  };

+  handleSearchSelection = (selection, store) => {
+    this.setState({
+      searchSelection: selection ? selection.label : "", //Clear Button à fix
+      searchValue: selection ? selection.label : ""
+    });
+    if (selection != null){
+      store.dispatch(setSendBox(selection.label));
+    }
+  };

  async handleFetchToken() {
    if (!this.state.token) {
      const res = await fetch('https://directline.botframework.com/v3/directline/conversations', {
      method: 'POST',
      headers: {
        "Authorization": "secret token ;)"
      }});
      const { token } = await res.json();
      this.setState(() => ({ token }));
    }
  }

  render() {
    const { state: {
      store,
      token,
+      searchValue,
+      searchSelection
    } } = this;


    return (
      <div className="WebChat">
        <WebChatReact
          className="react-web-chat"
          onFetchToken={ this.handleFetchToken }
          store={ store }
          token={ token }
        />

+        <form className="form-inline">
+          <Searchbox
+            className="select"
+            value={searchSelection}
+            onChange={e => this.handleSearchSelection(e, store)}
+            inputValue={searchValue}
+            onInputChange={(e, action) => this.handleSearchInput(e, action, store)}
+          />
+          <button
+            id="submit"
+            onClick={ event => {
+              event.preventDefault();
+              store.dispatch(submitSendBox())
+            }}
+          >
+            Submit
+          </button>
+        </form>
      </div>
    );
  }
}

ImprovedSendBox.js

Can be found on Github

the result: Imgur If you have any questions, feel free to ask me :)


Solution

  • Unfortunately, there is no simple way to replace Web Chat's text input at the moment, but there is an issue open on GitHub regarding the future possibility of customizing the Send Box.

    Even though there isn't a supported method to replace the send box at the moment, one option without having to fork the repo would be to hide the Send Box and render a custom one right below Web Chat. However, if you followed this approach, you would have to handle suggested actions, file attachments, and speech functionality in addition to tying Web Chat's store to the components state. You would also lose out on a lot of Web Chat's styling options.

    If this is still something you'd like to pursue, here are some code snippets to get you started.

    SimpleSendBox

    import React from 'react';
    import setSendBox from "botframework-webchat-core/lib/actions/setSendBox";
    import submitSendBox from "botframework-webchat-core/lib/actions/submitSendBox";
    
    
    export default ({ store, value }) => (
      <div>
        <form>
          <input 
            onChange={ ({ target: { value }}) => store.dispatch(setSendBox(value)) } 
            placeholder="Type your message..." 
            value={ value }
          />
          <button 
            onClick={ event => {
              event.preventDefault();
              store.dispatch(submitSendBox())
            }} 
          >
            Submit
          </button>
        </form>
      </div>
    )
    

    App

    import React, { Component } from 'react';
    import WebChat from './WebChat';
    import SimpleSendBox from './SimpleSendBox'
    import { createStore } from 'botframework-webchat';
    import './App.css';
    
    class App extends Component {
    
      constructor(props) {
        super(props);
    
        this.state = {
          store: createStore({},
            () => next => action => {
              if (action.type === 'WEB_CHAT/SET_SEND_BOX') {
                this.setState({ value: action.payload.text })
              }
              return next(action);
            }),
          value: ""
        }
      }
    
      render() {
        return (
        <>
          <WebChat store={ this.state.store } styleOptions={{ hideSendBox: true }} />
          <SimpleSendBox store={ this.state.store } value={ this.state.value }/>
        </>
        );
      }
    }
    
    export default App;
    
    

    Hope this helps!