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;
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.
Related
I am new to react, I am trying to write a react component, component has several features.
user can input a random number, then number will be displayed in the
page too.
implement a button with text value 'start', once click the button,
the number value displayed will reduce one every 1second and the
text value will become 'stop'.
continue click button, minus one will stop and text value of button
will become back to 'start'.
when number subtract to 0 will automatically stop itself.
I have implemented the first and second feature. but when I try to click stop to stop number from reducing 1, it does not work.
I am wondering since I used type=true/false to indicate the state of type is start or stop. Because in the start state, number should automatically reduce 1. And on the stop state, reducing 1 should stop. So, the timer function should accurate according to the state of type.
Also I am not sure if I used clearInterval method right.
I really appreciate if someone could give me a hand.
code is here:
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [{ id: 1, number: "" }],
type: false
};
this.handleClick = this.handleClick.bind(this);
}
changeNumber = (e, target) => {
this.setState({
details: this.state.details.map(detail => {
if (detail.id === target.id) {
detail.number = e.target.value;
}
return detail;
})
});
};
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
var myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};
render() {
return (
<div>
{this.state.details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => this.changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => this.handleClick()}
value={this.state.type ? "stop" : "start"}
/>
</div>
);
})}
</div>
);
}
}
export default App;
You need to declare var myTimer outside of the handleClick() function.
So it's something like:
var myTimer;
...
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};
I saw there are already answered questions on how to add spinners during fetch requests.
However what I need is to stop showing the animation when the animation completes. The animation completes after the timeout is reached.
Also I have a best practice question.
It's a good practice to empty the resources on componentWillUnmount and clear there the timeout. In the code below I clear the timeout in a if condition, because it has to stop as the height of the element reaches the right level.
Is that ok as I did it? If now, how should it look like to have the same functionality in the componentWillUnmount lifecycle phase?
Here is the animation Component:
class Thermometer extends Component {
state = {
termFill : 0
};
componentDidMount() {
const interval = setInterval(() => {
this.setState({
termFill: this.state.termFill + 10
});
if (this.state.termFill === 110) {
window.clearInterval(interval);
}
}, 200)
}
render() {
const styles = {
height: `${this.state.termFill}px`
};
if (this.state.termFill < 100) {
return (
<section>
<div id="therm-fill" style={styles} />
[MORE CODE - SHORTENED FOR EASIER READING]
)
}
};
And here is the Component that has to appear after the animation disappears.
The steps are like this:
A user enter and uses this tool
The user clicks "calculate"
The animation appears instead or on top of the tool
When the animation completes, the animation Component disappears and the tool
is once again visible with its updated state (results of the
calculation).
class DiagnoseTool extends Component {
state = {
[OTHER STATES REMOVED TO KEEP THE CODE SHORTER]
wasBtnClicked: false
};
[OTHER RADIO AND CHECKBOX HANDLERS REMOVED TO KEEP THE CODE SHORTER]
onButtonClick = e => {
e.preventDefault();
this.calculate();
this.setState({
wasBtnClicked: true
})
};
addResult = () => {
const resultColor = {
backgroundColor: "orange"
};
let theResult;
if (this..... [CODE REMOVED TO HAVE THE CODE SHORTER]
return theResult;
};
calculate = () => {
let counter = 0;
let radiocounter = 0;
Object.keys(this.state).filter(el => ['cough', 'nodes', 'temperature', 'tonsillarex'].includes(el)).forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++
} else if (this.state.radioAge === "age45") {
radiocounter--
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
})
}
this.setState({
points: counter + radiocounter
});
};
render() {
const {cough, nodes, temperature, tonsillarex, radioAge, wasBtnClicked} = this.state;
return (
<Container>
<BackArrow />
[JSX REMOVED TO KEEP THE CODE SHORTER]
<div className="resultbox">
{
(wasBtnClicked) && this.addResult()
}
</div>
</div>
[HERE IS THE BUTTON]
<button
style={{height: "40px", width: "150px", cursor:"pointer"}}
type="submit"
className="calculateBtn"
onClick={this.onButtonClick}
disabled={!radioAge}
>CALCULATE</button>
</Container>
Add a boolean to your state and set it to false, when the user clicks the button set it to true, after doing the calculation set it to false.
calculate = () => {
let counter = 0;
let radiocounter = 0;
this.setState({
isLoading: true // set is loading to true and show the spinner
})
Object.keys(this.state)
.filter(el =>
["cough", "nodes", "temperature", "tonsillarex"].includes(el)
)
.forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++;
} else if (this.state.radioAge === "age45") {
radiocounter--;
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
});
}
this.setState({
points: counter + radiocounter,
isLoading: false // set it to false and display the results of the calculation
});
};
Example
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
timer = null;
constructor() {
super();
this.state = {
result: '',
isLoading: false
};
}
showContent = () => { this.setState({ isLoading: false, result: `7 + 5 = ${7 + 5}` })}
calculate = () => {
this.setState({
isLoading: true,
result: ''
});
this.timer = setTimeout(this.showContent, 5000);
}
componentWillUnmount = () => {
clearTimeout(this.timer);
}
render() {
return (
<div>
<p>7 + 5</p>
<p>{this.state.result}</p>
{ this.state.isLoading
? <p>Calculating...</p>
: <button onClick={this.calculate}>Calculate</button>
}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
I'm new to react. I want to write a component that shows shows a loading animation for 5 seconds when a button is pressed, and other times display a simple "hello world". Here is my code:
import React, { Component } from 'react';
import ReactLoading from 'react-loading';
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
class Test extends Component {
constructor(props) {
super(props);
this.state = {
data: [], // will hold the results from our ajax call
loading: false, // will be true when ajax request is running
}
}
test1() {
setTimeout(function() {
this.setState({ loading: true }, () => {
sleep(5000);
this.setState({loading: false});
});
}.bind(this), 0);
}
onClick = () => {
this.test1();
}
render() {
const { data, loading } = this.state;
return (
<div>
<button onClick={this.onClick}>
Load Data
</button>
{loading ? <ReactLoading className={'my-icon'} type={"spin"} color={"black"} height={'50%'} width={'100%'} delay={100}/> : 'HelloWorld'}
</div>
);
}
}
export default Test;
However, after pressing button and state change of {loading: true}, component doesn't update. It updates only after that 5 seconds of sleep, so i never see loading icon.
Any ideas how to fix this?
Thanks in advance.
You can do this, no need of external sleep function,
test1() {
this.setState({ loading: true }, () => setTimout(()=>{this.setState({loading: false})},5000))
}
What I want: when the timer hits 0 seconds, the app mounts one component and hides others.
What happens: nothing.
I'm working on React single page app. I'm having a problem with the behavior of the timer when it hits 0. I want it to hide the Questions and Timer components and show just the Results component. Right now, the logic is in timerZero, but I did try putting it in startTimer and/or clickStart, but none of those combinations worked.
I've also noticed that if you select answers after the timer hits 0, it will continue console logging "Time's up!" on every selection. Hitting submit after 0 seconds will still take you to the resultsDiv with the correct scores but does not hide the timer as instructed.
Repo: https://github.com/irene-rojas/pixar-react
App
import React, { Component } from 'react';
import './App.css';
import Timer from "./Timer";
import Questions from "./Questions/Questions.js";
import Results from "../src/Results";
class App extends Component {
state = {
totalTrue: 0,
totalFalse: 0,
showTimer: true,
showQuestions: false,
showResults: false,
}
clickStart = (event) => {
event.preventDefault();
console.log("start button clicked");
this.setState(
{showQuestions: true}
)
}
// submit button
handleFormSubmit = (event) => {
event.preventDefault();
console.log("submit button clicked");
this.setState(
{showResults: true,
showQuestions: false,
showTimer: false}
// timer still appears in resultsDiv
)
};
timerZero = () => {
if (this.state.timer === 0) {
this.setState(
{showResults: true,
showQuestions: false,
showTimer: false}
)
}
// nothing happens >:(
};
callbackHandlerFunction = ( selectedOption ) => {
const answerValue = selectedOption.value;
if (answerValue === true) {
this.setState({totalTrue: this.state.totalTrue + 1}, () => {
console.log(`New TotalTrue: ${this.state.totalTrue}`);
});
};
if (answerValue === false) {
this.setState({totalFalse: this.state.totalFalse + 1}, () => {
console.log(`New TotalFalse: ${this.state.totalFalse}`);
});
};
}
render() {
return (
<div className="parallax">
<div className="App">
<div className="wrapper">
<div className="headerDiv">
<h1>Pixar Trivia!</h1>
</div>
<div className="timerDiv">
<Timer
handleTimerClick={this.clickStart}
timeOut={this.timerZero}
/>
</div>
{this.state.showQuestions &&
<div className="questionSection">
<Questions
handleClickInParent={this.callbackHandlerFunction}
/>
<div>
<button onClick={this.handleFormSubmit}>Submit</button>
</div>
</div>
}
{this.state.showResults &&
<div className="resultsDiv">
<Results
totalTrue={this.state.totalTrue}
totalFalse={this.state.totalFalse}
/>
</div>
}
</div>
</div>
</div>
);
}
}
export default App;
Timer
import React, { Component } from 'react';
class Timer extends Component {
state = {
timer: 10
};
startTimer = (event) => {
this.timer = setInterval(() => this.setState({
timer: this.state.timer - 1}), 1000);
// onClick, load Questions
this.props.handleTimerClick(event);
};
stopTimer = () => {
clearInterval(this.timer);
console.log("Time's up!");
this.props.timeOut();
};
render() {
return (
<div className="Timer">
<div>{this.state.timer} seconds</div>
<button onClick={this.startTimer}>Start!</button>
{this.state.timer === 0 && this.stopTimer()}
</div>
);
}
}
export default Timer;
I found out what was wrong with your code, I'm just going to break up where the mistakes are.
App.js
// ...
/*
you were trying to read this.state.timer
which is not decalred in this component
*/
timerZero = () => this.setState(
{showResults: true,
showQuestions: false,
showTimer: false}
)
// ...
render() {
{/* ... */}
{this.state.showTimer && (
<div className="timerDiv">
<Timer
handleTimerClick={this.clickStart}
timeOut={this.timerZero}
/>
</div>
{/* ... */
)}
Timer.js
// ...
/*
I added `shouldComponentUpdate` lifecycle
with this, we stop the `Timer` component for rendering
and call `stopTimer` (instead of doing it inside the render method)
*/
shouldComponentUpdate() {
console.log(this.state.timer);
if (this.state.timer <= 0) {
this.stopTimer();
return false;
}
return true;
};
/*
Also added the a componentWillUnmount method for good practice
here if the component is unmounted the timer won't be running forever.
*/
componentWillUnmount() {
clearInterval(this.timer);
};
render() {
return (
<div className="Timer">
<div>{this.state.timer} seconds</div>
<button onClick={this.startTimer}>Start!</button>
{/* delete the call to `this.stopTimer` */}
</div>
);
}
Also as an extra, I recommend you to ignore the node_modules folder in a .gitignore file to make your projects leaner. all your project dependencies are listed in yarn.lock or package-lock.json so when I download your repo I get to download the same dependencies you are using.
cheers!!!
I'm currently working on a sequencer using the WAAClock module and using React. I've been taking the code from a slide show presentation here and a Codepen example here
Here's my current code I made in a component. It is basically taken from the slideshow:
import React, { Component } from 'react';
import waaclock from 'waaclock'
class Player extends Component {
constructor(props) {
super(props);
this.state = {
steps: [0, 0, 0, 0],
currentStep: 0,
playing: false
};
}
componentDidMount() {
this.context = new AudioContext();
this.clock = new waaclock(this.context);
}
handleTick({ deadline }) {
const { currentStep, steps } = this.state;
if (steps[currentStep % steps.length]) {
console.log("tick")
}
setTimeout(() => {
this.setState({ currentStep: currentStep + 1 });
}, deadline - this.context.currentTime);
}
handlePlayPress() {)
if (!this.state.playing) {
this.setState({
currentStep: 0,
playing: true
}, () => {
this.clock.start();
this.tickEvent = this.clock.callbackAtTime(
this.handleTick.bind(this),
this.context.currentTime
).repeat(0.47);
});
} else {
this.setState(
{ playing: false },
() => {
this.clock.stop();
this.tickEvent = null;
}
);
}
}
render() {
const { currentStep, playing, steps } = this.state;
return (
<div>
{`Current Step: ${currentStep % steps.length}`}
<button onClick={() => this.handlePlayPress()}>{playing ? 'Stop' : 'Play'}</button>
</div>
)
}
}
export default Player;
Once I press play, the currentstep will default to -1. I'm expecting it to tick through as a timer. 1, 2, 3, 4. Im having trouble with this. The logic makes sense to me from Vincent Riemers demonstration, but I've hit a wall and am unsure whats going wrong.
Trying to get something like this to work would also be of benefit but it doesnt seem to work for me:
Any help would be much appreciated. Thanks
I'm going to post a git repo of my own test as soon as I get home.
Why don't keep it simple and efficient?
let songiter = 0
setInterval(function(){
songiter++
if (songiter === 4){
synth(2,590,50)
}
if (songiter === 8){
synth(2,122,100)
}
if (songiter === 16){
synth(2,432,100)
}
if (songiter === 32){
synth(2,75,100)
songiter = 0
}
}, tempo.value)
// Audio synth: gain, frequency, duration
audio = new AudioContext()
function synth(w,x,y){
console.log(w+x+y)
v=audio.createOscillator()
u=audio.createGain()
v.connect(u)
v.frequency.value=x
v.type="square"
u.connect(audio.destination)
u.gain.value=w*0.01
v.start(audio.currentTime)
v.stop(audio.currentTime+y*0.001)
}
<input value="140" id="tempo">