Search code examples
javascriptreactjsnext.jsreact-hookshtml5-audio

React use hook inside eventListener


I have created the following hook to play audio in react:

"use client";

import { useEffect, useState } from "react";

export const 

useAudio = (url: string) => {
  const [audio, setAudio] = useState<HTMLAudioElement | null>(null);
  const [playing, setPlaying] = useState(false);

  const setVolume = (volume: number) => {
    if (audio) audio.volume = volume;
    console.log(audio);
  };

  const toggle = () => {
    setPlaying(!playing);
    console.log(audio);
  };

  useEffect(() => {
    playing ? audio?.play() : audio?.pause();
  }, [playing]);

  useEffect(() => {
    audio?.addEventListener("ended", () => audio?.play());
  }, [audio]);

  useEffect(() => {
    setAudio(new Audio(url));
    return () => {
      audio?.removeEventListener("ended", () => audio.play());
    };
  }, []);

  return { playing, toggle, setVolume };
};

When I console log the audio element inside the toggle function, I get the correct element, but in setVolume it is always null.

I set the volume in a eventListener mouseMove like this:

    document.addEventListener("mousemove", function (e) {
      let element = document.getElementById("follow");
      let left = e.pageX;
      let top = e.pageY;
      element!.style.left = left + "px";
      element!.style.top = top + "px";

      const distance = Math.sqrt(
        Math.pow(left - devPosition.current.x, 2) +
          Math.pow(top - devPosition.current.y, 2)
      );

      setVolume(1 - distance / diagonal.current);
    });

Can this happen because I call setVolume inside such a listener? If so, what can I do about that?


Solution

  • I tried your useAudio hook and I think that there is nothing wrong with the hook.

    For example a component like this works as expected and doesn't yield any error.

    "use client";
    
    import { useEffect } from "react";
    import { useAudio } from "./audioHook"
    
    export default function Home() {
      const controls = useAudio('./horse.mp3');
    
      useEffect(() => {
        console.log(controls.playing);
      }, [controls]);
    
      const workWithAudio = () => {
        controls.toggle();
      };
    
      const changeVolume = () => {
        controls.setVolume(0.2);
      };
      
      return (
        <main>
          <button onClick={workWithAudio}>
            The toggle button
          </button>
          <button onClick={changeVolume}>
            The volume button
          </button>
        </main>
      )
    }
    

    There could be issues in the way you handle the listener. If I understand correctly (for example look here How to add a 'mousemove' event listener to a component Cursor which is moved with the cursor pointer in ReactJS?) the preferred way to add a listener in a React component is by adding it to a DOM node, like this:

    "use client";
    
    import { useAudio } from "./audioHook"
    
    export default function Home() {
      const controls = useAudio('./horse.mp3');
    
      const handleMouseMove = (e: MouseEvent) => {
        let element = document.getElementById("follow");
        let left = e.pageX;
        let top = e.pageY;
        element!.style.left = left + "px";
        element!.style.top = top + "px";
    
        // const distance = Math.sqrt(
        //   Math.pow(left - devPosition.current.x, 2) +
        //     Math.pow(top - devPosition.current.y, 2)
        // );
    
        controls.setVolume(0.5);
      }
    
      return (
        <main onMouseMove={handleMouseMove}>
          <div id="follow">TEST</div>
        </main>
      )
    }
    
    

    I don't have access to the devPosition and diagonal variables that you use in your function, so I had to use a constant, but if you instantiate the listener in this way you are able to set the volume without issues.