I have a working countdown clock except for the fact that it begins counting down one 'turn' after it should. I have a button which starts the game and starts the clock. I press it and it retrieves and shows my random number (say 5) and displays my random number of inputs (5). But the countdown says 0. which is (20 * null) of the loading page (this.state loads as null). I press start again, and there is a new random number but the countdown begins counting down from (5 * 20) instead of the new random number. I am a bit at a loss. Any help would be greatly appreciated.
The whole code
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
let timer = null
class StartButton extends React.Component {
render(props) {
return (
<button onClick={this.props.onClick}>{this.props.name}</button>
)
}
}
class Header extends React.Component {
constructor() {
super();
this.state = {
ranNum: null,
restart: false,
timer: null
};
}
handleClick() {
clearTimeout(timer);
this.clearForm();
this.generateRanNum();
this.generateInputs();
// this.changeToRestartText()
this.countdownClock();
}
generateRanNum = () => {
let ranNum = Math.floor(Math.random() * 20);
this.setState({
ranNum: ranNum,
})
}
clearForm = () => {
if(this.state.restart === true){
const inputLi = <Input />;
inputLi.map(element => {
element.remove()
});
const subButton = <SubmitButton />
subButton.remove()
}
}
countdownClock = async () => {
const startingNum = this.state.ranNum * 20;
for(let i = startingNum; i >= 0; i--) {
await new Promise(resolve => {
timer = setTimeout(() => {
this.setState({
timer: i
})
resolve()
}, 1000)
});
}
}
generateInputs = () => {
const inputs = []
for(let i = 1; i <= this.state.ranNum; i++){
inputs.push(
<Input type='text' className='textInputs' />
)
}
return inputs;
}
generateSubButton = () => {
return (
<SubmitButton name='Submit Button' />
)
}
render() {
return (
<div>
<header>
<div className="headerContainer">
<div id="countdown">
<Countdown name='Countdown: ' countdown={this.state.timer} />
</div>
<div className="flex-item-header">
<StartButton
name={!this.state.restart ? 'Start Button' : 'Restart Button'}
onClick={() => this.handleClick()}
/>
</div>
<div>
<DisplayCount name='Word Count: ' count={this.state.ranNum} />
</div>
</div>
</header>
<section>
<div className="flex-main-item">
<ul>
{this.generateInputs()}
</ul>
{this.generateSubButton()}
</div>
</section>
</div>
)
}
}
class SubmitButton extends React.Component {
render(props) {
return (
<button name={this.props.name} onClick={this.props.onClick}>
</button>
)
}
}
class DisplayCount extends React.Component {
render() {
return (
<p>
{this.props.name}
<span>{this.props.count}</span>
</p>
)
}
}
class Countdown extends React.Component {
render(props) {
return (
<p>
{this.props.name}
<span>{this.props.countdown}</span>
</p>
)
}
}
You didn't post your code for generateRanNum but unless it's setting the state, then the first time you run this.countdownClock(), state.ranNum is still null from the initalization of the component. You need to set that state first before you start counting down.
Moral of story: Setting component state in React is async.
You set ranNum state in generateRanNum() then you read this.state.ranNum in countdownClock(), expecting it to be up-to-date. Well, it’s not because setState() is async.
I guess you’re aware of the concept of component lifecycle. Any call to setState() will cause a round of call to lifecycle hook methods. But these calls are buffered and batch applied for performance reason, thus async.
this.state is not updated until componentWillUpdate() of "next round" is called. Yet you access it prematurely in "this round". Thus the countdown lags behind one turn, cus it indeed reads the old value.
Quick fix to your code:
const ranNum = this.generateRanNun();
this.countdownClock(ranNum);
Cheers.
Related
I'm practice send state to Child-component - Parent-Component - Another-Child-Component.
and I was stuck on my practice,
First, I was successful to make react counter like this
child component
Second, I want to send counter value to another component.
I know how to send state to another child component.
But i stuck my work.
Send state to another component
when i first clicked + button, it works only First child.
and then, when i clicked once more, it works another child too(not match number)
How can I dealing this problem?
This is my code.
// This is Counter.js
class Counter extends Component {
state = {
counter: 0
}
handleIncrement = () => {
this.setState(({ counter }) => ({
counter: counter + 1
}))
this.props.handleCounter(this.state.counter)
}
handleDecrement = () => {
this.setState(({counter}) => ({
counter: counter - 1
}))
this.props.handleCounter(this.state.counter)
}
render() {
return (
<div>
<h1>Counter</h1>
<h3>{this.state.counter}</h3>
<button onClick={this.handleIncrement}>+</button>
<button onClick={this.handleDecrement}>-</button>
</div>
)
}
}
// This is App.js file
import Counter from './components/counter';
import Sent from './components/sent'
class App extends React.Component {
state = {
counter: this.handleCounter
}
handleCounter = (counter) => {
console.log("Received Count 1 ")
this.setState({
counter: counter
})
}
render() {
return (
<div className="App">
<Counter handleCounter={this.handleCounter} />
<Sent result={this.state.counter} />
</div>
);
}
}
// This is Sent.js file
import React, { Component } from 'react'
class Sent extends React.Component {
render() {
return (
<div>
<h2>Result ==> {this.props.result}</h2>
</div>
)
}
}
I have an auction section in my React app, but can't quite get it to work. I would like for the page to render the added auction amount to the original with the starting bid. I've tried doing this without state and have gone through various iterations and haven't gotten to work.
Below you'll find the code of my most recent code iteration from my Auction component:
import React from "react"
import AuctionItems from "../AuctionItems"
class Auction extends React.Component {
constructor() {
super()
this.state = {
bid: 0
}
this.addHundred = this.addHundred.bind(this)
}
addHundred() {
this.setState((state) => {
console.log ({bid: state.bid + 100})
})
}
render() {
const items = AuctionItems.map(item => {
return (
<div>
<h4>{item.name}</h4>
<h4>${item.starting_bid}</h4>
<button onClick={this.addHundred}>Bid $100</button>
</div>
)
})
return (
<div>
<h1>Auction</h1>
{items}
</div>
)
}
}
export default Auction
Your state needs to be a bit more complex because you want to track bids for each item. The following uses a bids piece of your state that stores each item's bid keyed by the item name.
import React from "react"
import AuctionItems from "../AuctionItems"
class Auction extends React.Component {
constructor() {
super()
this.state = {
bids: {}
}
this.addHundred = this.addHundred.bind(this)
}
addHundred(item) {
this.setState((state) => ({
bids: {
...state.bids,
[item.name]: (state.bids[item.name] || 0) + 100
}
}));
}
render() {
const items = AuctionItems.map(item => {
return (
<div>
<h4>{item.name}</h4>
<h4>
${item.starting_bid + (this.state.bids[item.name] || 0)}
</h4>
<button onClick={() => this.addHundred(item)}>
Bid $100
</button>
</div>
)
})
return (
<div>
<h1>Auction</h1>
{items}
</div>
)
}
}
export default Auction
I have a Notification component that should close itself after a few seconds and call the onClose prop:
function Notification(props) {
console.log("Notification function component called");
setTimeout(() => {
props.onClose();
}, 4000);
return (
<div>
{props.children}
<button onClick={props.onClose}>Close</button>
</div>
);
}
In my App, I have a state that holds notifications object and I map through them.
class App extends React.Component {
constructor() {
super();
this.pushNotification = this.pushNotification.bind(this);
}
state = {
notifications: {}
};
pushNotification() {
const id = uuid();
const newNotifications = { ...this.state.notifications };
const date = new Date();
newNotifications[id] = {
id,
date: JSON.stringify(date)
};
this.setState({
notifications: newNotifications
});
}
removeNotification(id) {
console.log("removeNotification");
const newNotifications = { ...this.state.notifications };
delete newNotifications[id];
this.setState({
notifications: newNotifications
});
}
render() {
return (
<div className="App">
<button onClick={this.pushNotification}>Push notification</button>
{Object.keys(this.state.notifications).map(
(notificationIndexKey, index) => {
return (
<Notification
originalKey={JSON.stringify(index)}
key={notificationIndexKey}
onClose={() => {
console.log("Notfication fired on close");
this.removeNotification(notificationIndexKey);
}}
>
Notification{" "}
{this.state.notifications[notificationIndexKey].date}
</Notification>
);
}
)}
</div>
);
}
}
I've noticed that if I push multiple notifications in my state, the setTimout is initialized multiple times (which makes sense since render it's called every time the state is updated)
My question is, how would you recommend optimizing this so that the timeout to be invoked only once.
One method that I've tried is to create an array with items that I've removed and check before I call the prop.
Sandbox here: https://codesandbox.io/s/6y3my2y2jr
You should apply that side-effect when the component has mounted.
Currently your code will do this on render.
The render function can be called multiple times.
This code should reflect the correct changes.
class Notification extends React.Component {
componentDidMount() {
setTimeout(this.props.onClose, 4000);
}
render() {
return (
<div>
{props.children}
<button onClick={props.onClose}>Close</button>
</div>
);
}
}
You can do this by keeping a class property say notificationTimer initially set to null and can modify your Notification functions as:
function Notification(props) {
console.log("Notification function component called");
if (!this.notificationTimer)
this.notificationTimer = setTimeout(() => {
props.onClose();
}, 4000);
}
return (
<div>
{props.children}
<button onClick={props.onClose}>Close</button>
</div>
);
}
And in your close function you can do something like this:
onClose() {
// Your code.
if (this.notificationTimer) {
clearTimeout(this.notificationTimer);
this.notificationTimer = null;
}
}
This will not let you create multiple timers.
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!!!
Got a nagging issue and was wondering if anyone can shed some light.
I made a function that automates the routing for my react app...but i am trying to attach a button to this function to ensure it starts and stops on button click. However, when i try the code below...nothing happens
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.handleClick}>
Toggle
</button>
........
but when i try the second code, the tabbing function starts onClick of the button but of course doesn't stop when you click the button again.
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.tabControl}>
Toggle
</button>
Try using the current state instead of the optional callback inside setState:
handleClick() {
this.setState({ tabControl: !this.state.tabControl });
}
I'm not sure i fully get what you are trying to do but it seems to me that you forgot a condition.
You say if you invoke this method:
tabControl(props){
RoutePaths(this.props);
}
it works but won't stop.
Well, you are not running it conditionally.
In this method:
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
You are setting the tabControl state. I think you forgot to check it before running tabControl().
tabControl(props){
const {tabControl} = this.state;
tabControl && RoutePaths(this.props); // invoke of tabControl is true
}
Edit
After seeing the code for RoutePaths as you posted on comments:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
It seems to me that you will have another problem. you need the id of the interval that returned from setInterval in order to stop it, but you didn't stored it anywhere.
Quote from the docs:
... It returns an interval ID which uniquely identifies the interval,
so you can remove it later by calling clearInterval() ...
So you will need to store it somewhere and call clearInterval with ID.
this.intervalId = setInterval(() => {...});
And somewhere else in your class:
clearInterval(this.interval);
Edit #2
As a followup to your comment, here is a simple usage of interval with react:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
ticks: 0
};
}
onStart = () => {
this.intervalId = setInterval(() => {
this.setState({ ticks: this.state.ticks + 1 })
}, 500);
}
onStop = () => {
clearInterval(this.intervalId)
}
render() {
const { ticks } = this.state;
return (
<div>
<button onClick={this.onStart}>Start</button>
<button onClick={this.onStop}>Stop</button>
<div>{ticks}</div>
</div>
);
}
}
ReactDOM.render(<Timer />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
So you can try this approach,
RoutePaths will return the interval id:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
return setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
and tabControl will store the id and conditionally call or clear the interval:
tabControl() {
const { tabControl } = this.state;
if (tabControl && this.intervalId) { // i'm not sure this is the condition you want, but you can play with it
clearInterval(this.intervalId);
} else {
this.intervalId = RoutePaths(this.props);
}
}
I haven't tested this code but i think it can lead you to a good start.
You don't need tabControl state for what you are trying to do. However, you need to call clearInterval somewhere. Change your handleClick to something like this:
handleClick() {
// change RoutePath to return the id that setInterval returns.
if (this.routePathInterval) {
clearInterval(this.routePathInterval);
this.routePathInterval = null;
} else {
this.routePathInterval = RoutePath(this.props);
}
}
Also, when you call clearInterval and then start it again, your index will start over from zero. You may want to keep the current index in state and pass it to RoutePaths, if you want to resume from the index that you were on.
edit:
On second thought, you don't need to keep the index in state, since you don't want to trigger a re-render when you increment it. However, you should make index an instance variable and make RoutePath an instance method of your App component.
First, initialize this.index = 0; in your constructor and then:
routePaths() {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let interval = 3000;
return setInterval(() => {
this.props.history.push(pathUrls[index]);
this.index = (this.index + 1) % paths;
}, interval);
}