Search code examples
javascriptreactjsnull

What could be causing this error: Cannot read properties of null (reading 'play') TypeError: Cannot read properties of null (reading 'play')?


I am currently learning js through udemy and video tutorials, and been having many successes and many struggles. On thing I am working on is using a button to play an mp3 audio file. Right now, it works and it plays, but I would like to put it in a variable / object of some sort so I can add a pause method. However, whenever I try to store it, I get this error not at build but when I click on the button:

Cannot read properties of null (reading 'play') TypeError: Cannot read properties of null (reading 'play')

This is my code that works:

import sound from './Pics/Senorita.mp3'

function main({  }) {
  const AudioClick = () => {  
    new Audio(sound).play();
   };
    return (
       <div style={{ display: "flex", justifyContent: "center", 'fontSize': '40px'}}>
          <BsFillPlayCircleFill onClick={AudioClick}></BsFillPlayCircleFill>
       </div>
   );
}

export default main;

As you can see here, although this works it simply starts a new audio file, so I am unable to access it again. If I press the button multiple times, it will just overlap.

This is one of many solutions I have attempted: let x = document.getElementById(sound); x.play();

but most things I try give the same error. This makes me think it is an issue with the sorce, but I am not sure because it works well in the first method as shown. Any advice or suggestions would be greatly appreciated. I am new to posting on stack overflow, so I hope this question is appropriately written, feel free to leave constructive feedback.

Thank you everyone for your help!


Solution

  • If you want to assign your audio element to a variable in React, you probably need to add hooks to your application: in this case the useState and the useEffect hooks. You can find a lot of information about hooks on the web, so I'll just focus on solving your problem. (Anyway, if you want to read something more about hooks, start here: https://legacy.reactjs.org/docs/hooks-state.html).

    Let's start by declaring a setState hook that we'll use to set your variable

    const [audioElement, setAudioElement] = useState();
    

    To make sure this code only runs once, so you only have one instance of your audio element, we set its value inside a useEffect hook, with an empty array as a dependency.

    useEffect(() => {
      setAudioElement(new Audio('https://file-examples.com/storage/fe91f66a2a65f065f9ba741/2017/11/file_example_MP3_700KB.mp3'));
    }, []);
    

    (I've found the audio source on this website, just to make sure we have a file that works https://file-examples.com/index.php/sample-audio-files/sample-mp3-download/).

    At this point we can add the two methods that will start and stop the sound.

    // start
    const AudioStartClick = () => {  
      audioElement.play();
    };
    // stop
    const AudioStopClick = () => {  
      audioElement.pause();
    };
    

    Bonus

    As explained here https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement/Audio, we are not 100% sure that an audio element is able to play right away, so we might add an event listener to make sure we can use that element.

    In order to accomplish this, we need to add a new useState hook to manage a Boolean, and a new useEffect hook to listen to the event.

    const [canPlay, setCanPlay] = useState(false);
    useEffect(() => {
      audioElement?.addEventListener("canplaythrough", (event) => {
        setCanPlay(true);
      });
    }, [audioElement]);
    

    Now that we have this information, we just need to add a couple of lines to make sure that we don't call play or stop on an element that is not capable of playing.

    // start
    const AudioStartClick = () => {
      if (!canPlay) return;
      audioElement.play();
    };
    // stop
    const AudioStopClick = () => {
      if (!canPlay) return;
      audioElement.pause();
    };
    

    Finally, here is the complete code example:

    import React, { useState, useEffect } from 'react';
    
    export function App(props) {
    
      const [audioElement, setAudioElement] = useState();
      const [canPlay, setCanPlay] = useState(false);
    
      useEffect(() => {
        setAudioElement(new Audio('https://file-examples.com/storage/fe91f66a2a65f065f9ba741/2017/11/file_example_MP3_700KB.mp3'));
      }, []);
    
      // https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement/Audio
      useEffect(() => {
        audioElement?.addEventListener("canplaythrough", (event) => {
          setCanPlay(true);
        });
      }, [audioElement]);
    
    
      const AudioStartClick = () => {  
        if (!canPlay) return;
        audioElement.play();
       };
    
       const AudioStopClick = () => {  
        if (!canPlay) return;
        audioElement.pause();
       };
    
      return (
        <div className='App'>
          <button onClick={AudioStartClick}> Start </button>
          <button onClick={AudioStopClick}> Stop </button>
        </div>
      );
    }