0

Here is my parent component code fragment:

import Card  from 'react-bootstrap/Card';
import React ,{Component}  from 'react';
import MuteButton from './buttons/muteButton/MuteButton';

import './MediaPlayer.css'
class MediaPlayer extends Component {
    constructor(props) {
        super(props);
        this.handleMute=(()=>{
            //console.log(this.muteBtn);
            this.muteBtn.toggle(this.videoTag);
        })        
       
    }
    render() {
        return (
            <Card className="h-100 rounded">
                <video 
                    autoPlay
                    muted
                    className="card-body p-0 rounded w-100"
                    ref={instance => { this.videoTag = instance; }}>
                        <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4"></source>
                </video>
                <div className="playerOverlay p-1">
                    <div className="center text-white">
                    </div>
                    <div className="bg-secondary collapse controlBar p-1 rounded show text-white">
                        <div className="align-items-center d-flex flex-row justify-content-between p-0">
                            <MuteButton videoTag={this.videoTag}/>
                            <div><span ref={instance=>{this.elapseTimeSpan=instance;}}>00:00:00</span></div>
                        </div>

Here is my child code:

import React ,{Component,Fragment}  from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../Button.css';
class MuteButton extends Component {
    constructor(props) {
        super(props);
        var muted=true;
        this.state={"muted":muted};
        this.videoTag=this.props.videoTag;
        this.mutedSvg=(
            <Fragment>
                <svg 
                    xmlns="http://www.w3.org/2000/svg" 
                    width="24" 
                    height="24" 
                    style={{"fill": "white"}} viewBox="0 0 24 24">
                        <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
                </svg>
            </Fragment>
        );
        this.unMuteSvg=(
            <Fragment>
                <svg xmlns="http://www.w3.org/2000/svg" 
                    width="24" 
                    height="24" 
                    style={{"fill":"white"}} 
                    viewBox="0 0 24 24">";
                    <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
                </svg>
            </Fragment>
        )
        this.toggle=(()=>{
            muted=!muted;
            this.videoTag.muted=muted;
            this.setState({"muted":muted});
        })
    }
    render() {
        if (this.state.muted){
            return (
                <div className="btnlink" title="Mute" onClick={this.toggle}> 
                    {this.mutedSvg}
                </div>);
        } else {
            return (
                <div className="btnlink" title="Mute" onClick={this.clickHandler}> 
                    {this.unMuteSvg}
                </div>);
        }

    }
}
export default MuteButton;

I want to pass the video tag to the MuteButton component so that I can set the video tag to muted/unmute when the Mutebutton is clicked.

However, I got the following error message when I click on the MuteButton.

MuteButton.js:34 Uncaught TypeError: Cannot set property 'muted' of undefined

How can I fix the problem?

After getting advice from the experts, I almost done. The mute button working properly, however, I have set the muted equal to true, yet the video still does not be muted.

I create the sample on stackBlitz. Would you help to check?

The KNVB
  • 3,588
  • 3
  • 29
  • 54
  • 2
    I'd have the `muted` state on the parent - where the video is & instead pass down a function to the child as a prop that sets the new state instead of trying to pass down the element and mutating its properties directly – 95faf8e76605e973 Aug 11 '20 at 02:55
  • Do you mean I should manipulate the video tag in the Parent component? – The KNVB Aug 11 '20 at 02:58
  • Read [https://reactjs.org/docs/refs-and-the-dom.html](https://reactjs.org/docs/refs-and-the-dom.html). This should give you insight how to get references to DOM elements. – Jacob Smit Aug 11 '20 at 02:59
  • yeah, you can do `muted={this.state.muted}` on the parent – 95faf8e76605e973 Aug 11 '20 at 03:00
  • 1
    @95faf8e76605e973 is correct though, if you can get the same functionality using only props that is the way to go. DOM refs should only be used when you cannot achieve the functionality you require using the props (usually layout modifications). – Jacob Smit Aug 11 '20 at 03:02

1 Answers1

1

Cannot set property 'muted' of undefined

From this answer and this article we can learn that the callback you provided to the ref attribute will be executed when the component mounts. However, you are immediately passing it to a child component during the render process, thus it is undefined because the component has not mounted yet. To remedy this, you can probably have a state that keeps track of the refs, then on-mount: assign the refs to the state so that the child component can re-render with the correct ref assigned to its prop.

state = {
  videoRef: ""
};

componentDidMount(){
  this.setState({
    videoRef: this.video
  })
}

<video ref={(instance) => (this.video = instance)}>
...

<Child video={this.state.videoRef} />

https://codesandbox.io/s/so-react-html5-video-mute-toggler-using-refs-13srj?file=/src/App.js

Moreover, Dan Abramov said on this answer, that the correct ref would only be set if it indeed gets rendered. I'd be careful in using this implementation.


The better approach

As I indicated in my comment, the better approach to mute the video would be to

have the muted state on the parent - where the video is & instead pass down a function to the child as a prop that sets the new state instead of trying to pass down the element and mutating its properties directly

state = {
  muted: false
};

toggleMute = () => {
  this.setState({
    muted: !this.state.muted
  });
};

<video muted={this.state.muted}>
...

<Child toggleMute={this.toggleMute} />

CodeSandBox: https://codesandbox.io/s/so-react-html5-video-mute-toggler-y6b6q?file=/src/App.js:285-326

95faf8e76605e973
  • 13,643
  • 3
  • 24
  • 51
  • I have updated the question, would you help to check? – The KNVB Aug 11 '20 at 04:29
  • 1
    @TheKNVB I took a look at your StackBlitz. It looks like you are reinitializing your state on your constructor thus overriding the previous state... to remedy this: just include all the state initializations on 1 line. `this.state={ muted:true, "elapseTime":"00:00:00"};` https://stackblitz.com/edit/react-vzcmhv?file=MediaPlayer.js – 95faf8e76605e973 Aug 11 '20 at 04:35