Search code examples
javascriptreactjsspeech-recognition

How to implement React-Speech-Recognition AND search functionality within an input field? (React)


I'm trying to implement React-Speech-Recognition and search functionality within one and the same text input field in order to give users also the possibility to insert their search term by speaking instead of just typing it via keyboard.

The search functionality works so far in the case of entering search terms via keyboard.

However, as soon as I insert "transcript" into the input element as "<input value={(inputSearch, transcript)}" and into the Link element as "<Link to={/search/${(inputSearch, transcript)}}>" the search functionality doesn't work any more in the case of entering search terms via keyboard. The input via keyboard isn't renderd anymore...

How can I implement both functionalities, namely rendering/searching terms by speaking and typing it via keyboard?

Here's the code so far:

import React, { useState } from "react";
import "../Header.css";
import MenuSharpIcon from "@mui/icons-material/MenuSharp";
import SearchSharpIcon from "@mui/icons-material/SearchSharp";
import MicSharpIcon from "@mui/icons-material/MicSharp";
import VideoCallOutlinedIcon from "@mui/icons-material/VideoCallOutlined";
import NotificationsNoneSharpIcon from "@mui/icons-material/NotificationsNoneSharp";
import { Avatar } from "@mui/material";
import { Link } from "react-router-dom";

import SpeechRecognition, {
  useSpeechRecognition,
} from "react-speech-recognition";

function Header() {
  const [inputSearch, setInputSearch] = useState("");

  const handleClick = (e) => {
    if (inputSearch.trim().length < 1) {
      e.preventDefault();
    }
    setInputSearch("");
  };

  const {
    transcript,
    listening,
    resetTranscript,
    browserSupportsSpeechRecognition,
  } = useSpeechRecognition();

  if (!browserSupportsSpeechRecognition) {
    return <span>Browser doesn't support speech recognition.</span>;
  }

  return (
    <div className="header">
      <div className="headerLeft">
        <MenuSharpIcon />
        <Link to="/">
          <img
            className="headerLogo"
            src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/YouTube_Logo_2017.svg/1024px-YouTube_Logo_2017.svg.png?20220605194644"
            alt="YouTube Logo"
            onClick={(e) => setInputSearch("")}
          />
        </Link>
      </div>

      <div className="headerInput">
        <input
          onChange={(e) => setInputSearch(e.target.value)}
          value={inputSearch}
          placeholder="Search"
          type="text"
        />
        <Link to={`/search/${inputSearch}`}>
          <SearchSharpIcon
            className="headerInputSearchIcon"
            onClick={handleClick}
          />
        </Link>
        <MicSharpIcon
          className="headerInputMicIcon"
          onClick={SpeechRecognition.startListening}
        />
      </div>

      <div className="headerRight">
        <div className="headerRight1" data-tooltip="Create">
          <VideoCallOutlinedIcon className="headerRightIcon" />
        </div>
        <div className="headerRight2" data-tooltip="Notifications">
          <NotificationsNoneSharpIcon className="headerRightIcon" />
        </div>
        <div className="headerRight3">
          <Avatar className="headerRightIcon">ET</Avatar>
        </div>
      </div>
    </div>
  );
}

export default Header;

Any hints much appreciated!


Solution

  • I finally solved the problem after reconsidering it many times and splitting it into single logical steps. I also refactored the code as shown below.

    import React, { useState, useEffect } from "react";
    import "../Header.css";
    import MenuSharpIcon from "@mui/icons-material/MenuSharp";
    import SearchSharpIcon from "@mui/icons-material/SearchSharp";
    import MicSharpIcon from "@mui/icons-material/MicSharp";
    import VideoCallOutlinedIcon from "@mui/icons-material/VideoCallOutlined";
    import NotificationsNoneSharpIcon from "@mui/icons-material/NotificationsNoneSharp";
    import { Avatar } from "@mui/material";
    import { Link } from "react-router-dom";
    
    import SpeechRecognition, {
      useSpeechRecognition,
    } from "react-speech-recognition";
    
    function Header() {
      const [inputSearch, setInputSearch] = useState("");
    
      const handleClick = (e) => {
        if (inputSearch.trim().length < 1) {
          e.preventDefault();
        }
        setInputSearch("");
      };
    
      const { transcript, listening, browserSupportsSpeechRecognition } =
        useSpeechRecognition();
    
      useEffect(() => {
        setInputSearch();
      }, [listening]);
    
      if (!browserSupportsSpeechRecognition) {
        return <span>Browser doesn't support speech recognition.</span>;
      }
    
      return (
        <div className="header">
          <div className="headerLeft">
            <MenuSharpIcon />
            <Link to="/">
              <img
                className="headerLogo"
                src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/YouTube_Logo_2017.svg/1024px-YouTube_Logo_2017.svg.png?20220605194644"
                alt="YouTube Logo"
                onClick={(e) => setInputSearch("")}
              />
            </Link>
          </div>
    
          <div className="headerInput">
            {!listening ? (
              <input
                onChange={(e) => setInputSearch(e.target.value)}
                value={inputSearch}
                placeholder="Search"
                type="text"
              />
            ) : (
              <input value={transcript} />
            )}
            <Link to={`/search/${inputSearch || transcript}`}>
              <SearchSharpIcon
                className="headerInputSearchIcon"
                onClick={handleClick}
              />
            </Link>
            <MicSharpIcon
              className="headerInputMicIcon"
              onClick={SpeechRecognition.startListening}
            />
          </div>
    
          <div className="headerRight">
            <div className="headerRight1" data-tooltip="Create">
              <VideoCallOutlinedIcon className="headerRightIcon" />
            </div>
            <div className="headerRight2" data-tooltip="Notifications">
              <NotificationsNoneSharpIcon className="headerRightIcon" />
            </div>
            <div className="headerRight3">
              <Avatar className="headerRightIcon">ET</Avatar>
            </div>
          </div>
        </div>
      );
    }
    
    export default Header;

    The main takeaway is that I had to update the DOM by using the useEffect Hook and an inline if-else with conditional operator in order to distinguish when to render which input.