react-speech-recognition for multiple fields

Viewed 246

I'm making an input form and I would like to use the React Speech Recognition package to give users the option to record their input instead of typing it for two fields in the form - "review" and "order". I've made two versions of my dictaphone, as ReviewDict and OrderDict, and pass them respectively a "changeReview/changeOrder" prop to update the field.

However, whenever I click to start recording on one, it starts them both, and pulls the transcript of what I say into both input fields.

Is there a simple way to make the button for each only effect that instance of the package?

Here's the relevant parts of my code:

Input form:

              <TextField
                value={postData.review}
                onInput={(e) =>
                  setPostData({ ...postData, review: e.target.value })
                }
              />
            </div>
            <ReviewDict changeReview={dictUpdate} />      
                  
            <div>
              <TextField
                value={postData.order}
                onInput={(e) =>
                  setPostData({ ...postData, order: e.target.value })
                }
              />
            </div>
            <OrderDict changeOrder={orderUpdate} />

And then my dictaphones look like this:

import React, { useState } from "react";
import { CardContent } from "@mui/material";
import { useEffect } from "react";
import MicIcon from "@mui/icons-material/Mic";
import MicOffIcon from "@mui/icons-material/MicOff";

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


export const ReviewDict = ({ changeText, changeOrder, changeReview }) => {
  const [reviewText, setReviewText] = useState("");

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

  useEffect(() => {
    setReviewText(" ");

    changeReview(transcript)
  }, [transcript]);

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

  return (
    <div>
      <CardContent sx={{ paddingTop: 5, fontSize: 25 }}>
        <center>
          {!listening ? (
            <>
              {" "}
              <span onClick={SpeechRecognition.startListening}>
                <MicIcon fontSize="normal" /> review
              </span>
            </>
          ) : (
            <>
              <span onClick={SpeechRecognition.stopListening}>
                <MicOffIcon fontSize="normal" />
              </span>
            </>
          )}
        </center>
      </CardContent>
    </div>
  );
};

and

import React, { useState } from "react";
import { CardContent } from "@mui/material";
import { useEffect } from "react";
import MicIcon from "@mui/icons-material/Mic";
import MicOffIcon from "@mui/icons-material/MicOff";
import RestartAltIcon from "@mui/icons-material/RestartAlt";

import { Card, Rating, TextField, Box } from "@mui/material";

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

export const OrderDict = ({ changeText, changeOrder, changeReview }) => {
  const [reviewText, setReviewText] = useState("");

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

  useEffect(() => {
    setReviewText(" ");

    changeOrder(transcript);
  }, [transcript]);

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

  return (
    <div>
      <CardContent sx={{ paddingTop: 5, fontSize: 25 }}>
        <center>
 


          {!listening ? (
            <>
              {" "}
              <span onClick={SpeechRecognition.startListening}>
                <MicIcon fontSize="normal" /> order
              </span>
            </>
          ) : (
            <>
              <span onClick={SpeechRecognition.stopListening}>
                <MicOffIcon fontSize="normal" />
              </span>
            </>
          )}


{/*
          <br />
          {transcript && (
            <>
            <span onClick={resetTranscript}>
              <RestartAltIcon fontSize="large" />
              Re-record
            </span>
            </>
          )}
          */}
        </center>
      </CardContent>
    </div>
  );
};

Any help much appreciated!

1 Answers

Ok, the solution to this came to me while I was showering!

The problem was that the controls for the speech recognition tool are global, so calling startListening on one instance was calling it on all of them, and because my components were both rendered on the page, I couldn't do one without the other - so I just needed to only render at most one at a time.

So I just initialised a fieldDict variable with state like so:

  const [fieldDict, setFieldDict] = useState(null);

And then conditionally render either the start recording button, or an identical looking button to setFieldDict to the right field, depending on the current fieldDict value, like so:

            {fieldDict === "review" ? (
              <ReviewDict changeReview={dictUpdate} />
            ) : (
                <center>
                  <MicIcon onClick={() => setFieldDict("review")} />
                </center>
            )}

In the speech-to-text component, I then use useEffect to begin recording as soon as the component is rendered, like so:

  useEffect(() => {
    SpeechRecognition.startListening()
  }, []);

This means from a user's standpoint, it's identical to what I was trying to do - there's a recording button that begins recording when you click it and transcribes your text to the correct field, but behind the scenes I'm only ever rendering one instance of the speech to text component at a time, so I avoid accidentally triggering instances of it when I don't mean to.

Related