skip to Main Content

I’m trying to generate <Audio> components with map:

import React, { useState, useRef, createRef } from 'react';

const audios = [{ src: '/fire.mp3' }, { src: '/crickets.mp3' }];

function Audio({ audioRef }) {
  const { src, ref } = audioRef;

  function handleVolumeChange(value) {
    ref.current.volume = value / 100;
  }

  return (
    <audio ref={ref} loop>
      <source src={src} type="audio/mpeg" /> Your browser does not support the
      audio element.
    </audio>
    // `handleVolumeChange` is used here
  );
}

export const App = ({ name }) => {
  const audioRefs = useRef(
    audios.map((audio) => ({
      ...audio,
      ref: createRef(),
    }))
  );
    
  return (
    <>
      {audioRefs.current.map((audioRef, index) => (
        <Audio key={audioRef.src} audio={audioRef} />
      ))}
    </>
  );
};

However, I’m getting this error:

Cannot destructure property ‘src’ of ‘audioRef’ as it is undefined.

Why is audioRef undefined when audioRefs is an array?

[{…}, {…}]
  0: Object
    ref: Object
    src: "/fire.mp3"
    <prototype>: Object
  1: Object
    ref: Object
    src: "/crickets.mp3"
    <prototype>: Object

Working code on StackBlitz.

Note: I need the ref to control the volume of the <audio> element.

2

Answers


  1. Presumably this is the line producing the error?:

    const { src, ref } = audioRef;
    

    In which case audioRef is a prop on the Audio component here:

    function Audio({ audioRef }) {
    

    Where do you use that Audio component? Here:

    <Audio key={audioRef.src} audio={audioRef} />
    

    As you can see, you’re not passing a prop called audioRef. You’re passing a prop called audio (and a key).

    The prop names need to match.

    Either change the expected prop name to match what’s being passed:

    function Audio({ audio }) {
    

    Or change what’s being passed to match what’s expected:

    <Audio key={audioRef.src} audioRef={audioRef} />
    
    Login or Signup to reply.
  2. You’ll need to use React.forwardRef() in <Audio /> to be able to forward to <audio>.


    Small demo using 2 test mp3‘s from https://onlinetestcase.com/mp3-file/.

    Press the button to toggle the volume between the default 1.0 and 0.2.

    const { useState, useRef, createRef, forwardRef } = React;
    
    const Audio = React.forwardRef((props, ref) => {
    
        const handleVolumeChange = () => {
            ref.current.volume = (ref.current.volume === 1 )
                ? 0.2 
                : 1.0;
        }
        
        return (
            <React.Fragment>
                <audio ref={ref} loop={true} controls={true}>
                    <source src={props.src} type="audio/mpeg" /> 
                </audio>
                <button onClick={handleVolumeChange}>Toggle volume</button>
                <br />
            </React.Fragment>
        );
    });
    
    const App = ({ name }) => {
        const audios = [
            { src: 'https://onlinetestcase.com/wp-content/uploads/2023/06/100-KB-MP3.mp3' }, 
            { src: 'https://onlinetestcase.com/wp-content/uploads/2023/06/500-KB-MP3.mp3' }
        ];
    
        const audioRefs = useRef(
            audios.map((audio) => ({
                ...audio,
                ref: createRef(),
            }))
        );
    
        return audioRefs.current.map((audioRef, index) => {
            return <Audio key={audioRef.src} {...audioRef} />;
        })
    };
    
    ReactDOM.render(<App />, document.getElementById("react"));
    html, body, #react { height: 100vh; width: 100vw; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search