nCardot
nCardot

Reputation: 6586

Add mp3 to React

I added an mp3 to the src folder in a project bootrstrapped with Create React App. I added a component for the audio file, Audio.js, which I'd like to play conditionally based on whether a prop playAlarm is true or not. The parent App.js passes the prop to child Timer.js, which renders Audio.js.

The Audio.js file is giving me a 'React' is defined but never used error, and I'm not sure why.

Audio.js:

import React, { Component } from 'react';

class Audio extends Component {
  constructor() {
    super();
    this.url = "./chime.mp3";
    this.audio = new Audio(this.url);
  }

  render() {

    return (
      this.audio
    );
  }
}

export default Audio;

In Timer.js, Audio is rendered like this: {props.playAlarm ? <Audio /> : null}

When I tested playing the audio, when playAlarm is set to true I get Uncaught RangeError: Maximum call stack size exceeded at the line with super() in Audio.js.

App.js:

import React, { Component } from 'react';
import Timer from './Timer';

class App extends Component {
  // ES6 class property/class field syntax allows you to remove constructor when just being used to initialize state
  state = {
    sessionDuration: 5, // TODO: change back to 1500 when testing done
    breakDuration: 3, // TODO: change back to 300 when testing done
    sessionTimeRemaining: 5, // TODO: change back to 1500 when testing done
    breakTimeRemaining: 3, // TODO: change back to 300 when testing done
    isSession: true,
    timerOn: false,
    sessionNumber: 0,
    playAlarm: false
  }

  // Using property initializer syntax to avoid need to bind, since arrow functions don't create their own this context and use value of enclosing context instead. transform-class-properties Babel plugin necessary to use this syntax (included in Create React App). Refer to https://itnext.io/property-initializers-what-why-and-how-to-use-it-5615210474a3 for more details

  // DURATION CHANGES

  decreaseBreakDuration = () => {
    // Conditional statement prevents decrease when break is at 1 minute
    if (this.state.breakDuration === 60) {
      return undefined;
    } else {
      this.setState({
        breakDuration: this.state.breakDuration - 60
      });
    }
  }

  increaseBreakDuration = () => {
    this.setState({
      breakDuration: this.state.breakDuration + 60
    });
  }

  decreaseSessionDuration = () => {
    // Conditional statement prevents decrease when session is at 5 minutes
    if (this.state.sessionDuration === 300) {
      return undefined;
    } else {
      this.setState({
        sessionDuration: this.state.sessionDuration - 60,
        sessionTimeRemaining: this.state.sessionTimeRemaining - 60
      });
    }
  }

  increaseSessionDuration = () => {
    this.setState({
      sessionDuration: this.state.sessionDuration + 60,
      sessionTimeRemaining: this.state.sessionTimeRemaining + 60
    });
  }

  manageBreak = () => {
    this.setState({
      playAlarm: false
    });
    this.time = setInterval(() => {
      this.setState({
        breakTimeRemaining: this.state.breakTimeRemaining - 1
      });
      if (this.state.breakTimeRemaining === 0) {
        this.handleBreakComplete();
      }
    }, 1000);
  }

  manageSession = () => {
    this.setState({
      playAlarm: false
    });
    // Every 1,000 ms (1 second), subtract 1 (a single second) from displayed sessionTimeRemaining. Assigned to this.time (scoped to entire class) in order to pass it to clearInterval() when pause button is clicked
    this.time = setInterval(() => {
      this.setState({
        sessionTimeRemaining: this.state.sessionTimeRemaining - 1
      });
      if (this.state.sessionTimeRemaining === 0) {
        this.handleSessionComplete();
      }
    }, 1000);
  }

  handleSessionComplete = () => {
    clearInterval(this.time);
    this.setState({
      playAlarm: true,
      sessionNumber: this.state.sessionNumber + 1
    })

    if (this.state.sessionNumber === 4) {
      this.handlePomodoroCycleDone();
    } else {
      this.setState({
        timerOn: false,
        sessionTimeRemaining: this.state.sessionDuration,
        breakTimeRemaining: this.state.breakDuration,
        isSession: !this.state.isSession,
      });
    }
  }

  handlePomodoroCycleDone = () => {
    // TODO: Display message in modal
    console.log('Great work! You finished a pomodoro cycle (four sessions). Time to relax.')
    // Change back to default values
    this.setState({
      isSession: true,
      timerOn: false,
      sessionDuration: 5, // TODO: change back to 1500
      breakDuration: 3, // TODO: change back to 300 when testing done
      sessionTimeRemaining: 5, // TODO: change back to 1500
    });
  }

  handleBreakComplete = () => {
    clearInterval(this.time);
    this.setState({
      timerOn: false,
      sessionTimeRemaining: this.state.sessionDuration,
      breakTimeRemaining: this.state.breakDuration,
      isSession: !this.state.isSession,
      playAlarm: true
    });
  }

  // PLAY, PAUSE, RESTART BUTTONS

  startTimer = () => {
    this.setState({
      timerOn: true,
    });

    if (this.state.isSession) {
      this.manageSession();
    } else {
      this.manageBreak();
    }
  }

  pauseTimer = () => {
    // Stops setInterval's calling its (setState) callback every 1000 ms
    clearInterval(this.time);

    this.setState({
      timerOn: false
    });
  }

  resetTimer = () => {
  // Stops setInterval's calling its (setState) callback every 1000 ms
  // TODO: Display 4 unchecked circle icons again
    clearInterval(this.time);
    this.setState({
      timerOn: false,
      sessionDuration: 5, // TODO: change back to 1500
      breakDuration: 3, // TODO: change back to 300 when testing done
      sessionTimeRemaining: 5, // TODO: change back to 1500
      breakTimeRemaining: 3, // TODO: change back to 300 when testing done
      sessionNumber: 0
    });
  }

  render() {
    return (
      <Timer
        breakDuration={this.state.breakDuration}
        sessionDuration={this.state.sessionDuration}

        decreaseBreakDuration={this.decreaseBreakDuration}
        increaseBreakDuration={this.increaseBreakDuration}
        decreaseSessionDuration={this.decreaseSessionDuration}
        increaseSessionDuration={this.increaseSessionDuration}

        sessionTimeRemaining={this.state.sessionTimeRemaining}
        breakTimeRemaining={this.state.breakTimeRemaining}
        timerOn={this.state.timerOn}
        sessionNumber={this.state.sessionNumber}

        isSession={this.state.isSession}

        startTimer={this.startTimer}
        pauseTimer={this.pauseTimer}
        resetTimer={this.resetTimer}

        playAlarm={this.state.playAlarm}
      />
    );
  };
}

export default App;

Also here's Timer.js:

import Audio from './Audio';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlay } from '@fortawesome/free-solid-svg-icons';
import { faPause } from '@fortawesome/free-solid-svg-icons';
import { faUndo } from '@fortawesome/free-solid-svg-icons';
import React from 'react';
import PomodoroIcons from './PomodoroIcons';
import DurationControls from './DurationControls';

const TimeFormat = require('hh-mm-ss');

const Timer = props => (

<div className="timer">

  <DurationControls
    breakDuration={props.breakDuration}
    sessionDuration={props.sessionDuration}

    increaseBreakDuration={props.increaseBreakDuration}
    decreaseBreakDuration={props.decreaseBreakDuration}

    increaseSessionDuration={props.increaseSessionDuration}
    decreaseSessionDuration={props.decreaseSessionDuration}
  />

  {/* TIME REMAINING */}
  <p className="time-remaining">
    {props.isSession ? TimeFormat.fromS(props.sessionTimeRemaining) : TimeFormat.fromS(props.breakTimeRemaining)}
  </p>

  {/* PLAY, PAUSE, RESTART BUTTONS */}
  <div className="bottom-btns">

    <div className={props.timerOn ? 'hidden' : ''}>
      <FontAwesomeIcon
        role="button"
        onClick={props.startTimer}
        icon={faPlay}
        className="btn bottom-btn"
        size="4x"
      />
    </div>

    <div className={props.timerOn === false ? 'hidden' : ''}>
      <FontAwesomeIcon
        role="button"
        onClick={props.pauseTimer}
        icon={faPause}
        className="btn bottom-btn"
        size="4x"
      />
    </div>

    <FontAwesomeIcon
      role="button"
      onClick={props.resetTimer}
      icon={faUndo}
      className="btn bottom-btn"
      size="4x"
    />

  </div> {/* End bottom-btns */}

  <PomodoroIcons sessionNumber={props.sessionNumber} />

  {props.playAlarm ? <Audio /> : null}
</div>

);

export default Timer;

Upvotes: 0

Views: 221

Answers (1)

hamobi
hamobi

Reputation: 8130

i dont follow everything going on here.. but at a glance this is an issue:

class Audio extends Component {
  constructor() {
    super();
    this.url = "./chime.mp3";
    this.audio = new Audio(this.url);
  }

  render() {

    return (
      this.audio
    );
  }
}

the call stack exceeded error is because you're entering into an infinite loop. You instantiate Audio inside of Audio which will make another Audio object and so on into infinity.

Upvotes: 2

Related Questions