JohnPix
JohnPix

Reputation: 1833

useState keeps the previous state

In my app I click Play button and audio should start playing, when it's over speech recognition should start listening, when it's over the message should be printed in console (it will be change to some other actions further).

My app also has a feature to stop recognition process if I click stop button, for that I use useState where false state changes to true.

But, I face an issue: when I try to stop listening by clicking Stop button, the state changes to true and it should stop here, but despite that I have if(isPlaying === false) condition, after true I get false in console as last action 🤷🏻‍♂️ Why it happens so, and how to fix it?

const [song] = useState(typeof Audio !== 'undefined' && new Audio())
  const [isPlaying, setIsPlaying] = useState(false)

  function playSong() {
    setIsPlaying(!isPlaying)
    if (listening) {
      SpeechRecognition.stopListening()
    }
    if (isPlaying === false) {
      song.src = 'https://raw.songToPlay.mp3'
      song.play()
      console.log('Song is playing: ' + isPlaying)
      song.addEventListener('ended', () => {
        console.log('Recognition is started: ' + isPlaying)
        SpeechRecognition.startListening()
      })
      recognition.addEventListener('audioend', () => {
        console.log('Recognition is finished: ' + isPlaying)
      })
    } else {
      console.log('When stop is clicked: ' + isPlaying)
    }
  }

  return (
    <div>
      <p>Microphone: {listening ? 'on' : 'off'}</p>
      <button onClick={playSong}>{isPlaying ? 'Stop' : 'Play'}</button>
      <p>{transcript}</p>
    </div>
  )

Output:

enter image description here

Upvotes: 0

Views: 105

Answers (1)

tripp
tripp

Reputation: 126

Logging the state from within playSong will log the state when the function was defined. This means that the value will be outdated, i.e. stale. This is because of a functional concept called closures. From Wikipedia “Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.”

So how should we log the current state when it is changed? By using the useEffect hook. This function will be re-run whenever any of the dependencies change.

You can also return a 'cleanup' function to remove listeners, etc.

CodeSandbox

  const [isPlaying, setIsPlaying] = useState(false);

  useEffect(() => {
    if (!isPlaying) {
      console.log("Starting to play");
      // add your event listener here to wait for song to end and start speech recognition
    } else {
      console.log("When stop is clicked: " + isPlaying);
      // stop your speech recognition here
    }

    return () => {
      // this is a cleanup function - you can use it to remove event listeners so you don't add them twice
    };
  }, [isPlaying]);

  return (
    <div>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? "Stop" : "Play"}
      </button>
    </div>
  );

Upvotes: 2

Related Questions