I'm pretty new to React and trying to write my first app to get a better understanding.
What I'm trying to build is a simple time tracking tool where the user can start and stop a work timer.
Here you can see the design I came up with:
If the user clicks on the "start" button the working time Timer component should update every second. If the user clicks then on the "take a break" button the timer should stop and instead the break time Timer component should start ticking.
I would like to reuse the Timer component for both working and break timer and just set different states.
I already managed to do this but I don't know if this is a nice way or if this can be improved and make it more generic?
My Tracker component looks like this:
class Tracker extends Component {
constructor(props) {
super(props);
this.state = {
workTime: 0,
breakTime: 0,
isRunning: false,
timerType: 'workTimer'
}
}
startTimer(type) {
this.setState({
isRunning: true,
timerType: type
});
this.timerInterval = setInterval(() => {
this.updateTimer()
}, 1000);
}
stopTimer() {
this.setState({
isRunning: false
});
clearInterval(this.timerInterval);
}
toggleBreak(type) {
this.setState({
timerType: type
});
if (!this.state.isRunning && this.state.timerType === 'breakTimer') {
this.startTimer('breakTimer');
} else if (this.state.isRunning && this.state.timerType === 'breakTimer') {
this.stopTimer();
this.startTimer('workTimer');
} else {
this.stopTimer();
this.startTimer('breakTimer');
}
}
updateTimer() {
let state = null;
if (this.state.timerType === 'workTimer') {
state = {
workTime: this.state.workTime + 1000
};
} else {
state = {
breakTime: this.state.breakTime + 1000
};
}
this.setState(state);
}
render() {
return (
<div className="tracker">
<Timer time={ this.state.workTime }/>
<Timer time={ this.state.breakTime }/>
<TimerControls
isRunning={ this.state.isRunning }
start={ () => this.startTimer('workTimer') }
stop={ () => this.stopTimer() }
toggleBreak={ () => this.toggleBreak('breakTimer') }
/>
</div>
);
}
}
Controls component:
class TimerControls extends Component {
constructor(props) {
super(props);
}
render() {
const {isRunning, start, stop, toggleBreak} = this.props;
return (
<div className="tracker__control">
<button onClick={ start } disabled={ isRunning }>Start</button>
<button onClick={ toggleBreak }>Break</button>
<button onClick={ stop } disabled={ !isRunning }>Stop</button>
</div>
);
}
}
Timer component:
class Timer extends Component {
constructor(props) {
super(props);
}
render() {
const { time } = this.props;
return (
<div className="tracker__timer">{ timeFormat(time) }</div>
);
}
}
Is there a way to get rid of the timerType conditions?
Related
Hi im trying to code a Timer. My problem is that the countdown should start after clicking a button and when that status is changed from start to stop and vise versa. I cant figure out how to handle it and which level to put it.
ADDITIONAL INFO:
When clicking the button, it goes to the method handler. Changes the status with setstate() but it renders at the end. Which is to late for the countdown to start.
Here is the Game Component:
import React, { Component } from 'react';
import './Game.css';
import Timer from './Timer'
class Game extends Component {
constructor() {
super();
}
state = {
buttonStatus: {status:"Start" , classButton:"Button ButtonBackgroundColorGrey" },
dotclass : "",
timer: 60
}
componentDidMount() {
this.timersignal();
}
buttonclick = () =>{
(this.state.buttonStatus.status === "Start")
? this.setState({buttonStatus:{status:"Stop",classButton:"Button ButtonBackgroundColorRed"},dotclass:"Dot"})
: this.setState({buttonStatus:{status:"Start",classButton:"Button ButtonBackgroundColorGrey"},dotclass:""})
this.componentDidMount();
}
timersignal = () => {
if(this.state.buttonStatus.status === "Stop") {
this.Interval = setInterval(() =>{
this.setState(() => ({
timer : this.state.timer - 1
}))
},1000)
console.log("didMount start")
}
else(
console.log("didMount stop")
)
}
render() {
return (
<div>
<div className="Body-Container">
<h2 className="States"> Time </h2>
<Timer buttonstate= {this.state.timer}/>
<button className={this.state.buttonStatus.classButton} onClick={this.buttonclick}>{this.state.buttonStatus.status}</button>
</div>
</div>
);
}
}
export default Game;
Here is the Timer Component:
import React, { Component } from 'react';
import "./Timer.css";
class Timer extends Component {
render() {
return (
<h3 className="Timer">{this.props.buttonstate}</h3>
);
}
}
export default Timer ;
You just need one method and call it in componentDidMount and on click.
timerToggle = () =>{
if((this.state.buttonStatus.status === "Start") {
this.setState({buttonStatus:{status:"Stop",classButton:"Button ButtonBackgroundColorRed"},dotclass:"Dot"})
clearInterval(this.Interval);
}else{
this.setState({buttonStatus:{status:"Start",classButton:"Button ButtonBackgroundColorGrey"},dotclass:""}) ;
this.Interval = setInterval(() =>{
this.setState(() => ({
timer : this.state.timer - 1
}))
},1000)
}
}
componentDidMount() {
this.timerToggle();
}
The Final Answer:
timerToggle = () =>{
if(this.state.buttonStatus.status === "Start") {
this.setState({buttonStatus:{status:"Stop",classButton:"Button ButtonBackgroundColorRed"},dotclass:"Dot"})
this.Interval = setInterval(() =>{
this.setState(() => ({
timer : this.state.timer - 1
}))
},1000)
}
else{
this.setState({buttonStatus:{status:"Start",classButton:"Button ButtonBackgroundColorGrey"},dotclass:""}) ;
clearInterval(this.Interval);
}
}
Please forgive me, I am new to programming and JavaScript/React...
This is the question from my assignment:
Make a counter application using React and Node.js. The user must have the ability to click a button to increase, decrease, or reset a counter. The app must have the following components: Display, DecreaseCount , IncreaseCount, ResetCount. Pass the appropriate functions to be used and current counter value to each component.
I'm not sure what the point is of creating components for those simple operations. I also don't understand what will make those arithmetical components unique if I'm passing them both a function and a value to work on. But I am assuming the point of the assignment is to show that you can pass state to a child, work on it within the child, and then pass the worked-on result back to the parent to be stored in its state.
Here is the main page, Display.js.
For now I'm just trying to get the add functionality to work:
import React, { Component } from 'react';
import IncreaseCount from './IncreaseCount';
import DecreaseCount from './DecreaseCount';
import ResetCount from './ResetCount';
class Display extends Component {
constructor(props) {
super(props);
this.increment = this.increment.bind(this);
this.state = {
count: 0
};
}
increment = numToInc => {
this.setState({ count: numToInc++ });
};
decrement = numToDec => {
this.setState({ count: numToDec-- });
};
reset = numToReset => {
numToReset = 0;
this.setState({ count: numToReset });
};
render() {
return (
<div>
<h2>{this.state.count} </h2>
<IncreaseCount count={this.state.count} operation={this.increment} />
<DecreaseCount count={this.state.count} operation={this.decrement} />
<IncreaseCount count={this.state.count} operation={this.reset} />
</div>
);
}
}
export default Display;
And here is the IncreaseCount component class:
import React, { Component } from 'react';
class IncreaseCount extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
buttonClick = () => {
this.setState({ count: this.props.count }); // I am pretty sure this isn't necessary
this.props.operation(this.state.count);
};
render() {
return <button onClick={this.buttonClick}></button>;
}
}
export default IncreaseCount;
It is not throwing any errors but is not changing the value of either the Increase count or the Display count properties. I was expecting both to be changing in lockstep. My goal is to send the incremented value back to Display to be displayed. Is there a problem with the way I've written and passed my increment function?
You need to use this.props.count within the IncreaseCount
class IncreaseCount extends Component {
buttonClick = () => {
this.props.operation(this.props.count);
};
...
}
A full example might look something like this:
class Display extends Component {
state = {
count: 0
};
increment = numToInc => {
this.setState({ count: numToInc + 1 });
};
decrement = numToDec => {
this.setState({ count: numToDec - 1 });
};
reset = () => {
this.setState({ count: 0 });
};
render() {
return (
<div>
<h2>{this.state.count} </h2>
<Operation
name="+"
count={this.state.count}
operation={this.increment}
/>
<Operation
name="-"
count={this.state.count}
operation={this.decrement}
/>
<Operation
name="Reset"
count={this.state.count}
operation={this.reset}
/>
</div>
);
}
}
class Operation extends Component {
buttonClick = () => {
this.props.operation(this.props.count);
};
render() {
return <button onClick={this.buttonClick}>{this.props.name}</button>;
}
}
Note that you don't have to pass the counter value to each Operation and use a functional setState:
increment = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
Using a single component like <Operation /> is certainly how I'd do it. However, per the requirements of the OP, I'm adding this example that uses all 4 components specified.
import React, { Component } from 'react';
class IncreaseCount extends Component {
render(props) {
return <button onClick={this.props.action}>+</button>;
}
}
class DecreaseCount extends Component {
render(props) {
return <button onClick={this.props.action}>-</button>;
}
}
class ResetCount extends Component {
render(props) {
return <button onClick={this.props.action}>reset</button>;
}
}
class Display extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.reset = this.reset.bind(this);
}
increment() {
this.setState({ count: this.state.count + 1 });
}
decrement() {
if (this.state.count > 0) {
this.setState({ count: this.state.count - 1 });
}
}
reset() {
this.setState({ count: 0 });
}
render() {
return (
<div>
<h2>{this.state.count}</h2>
<DecreaseCount count={this.state.count} action={this.decrement} />
<IncreaseCount count={this.state.count} action={this.increment} />
<ResetCount count={this.state.count} action={this.reset} />
</div>
);
}
}
export default Display;
This version also prevents the counter from going below 0.
I'm attempting to write a HOC that changes its state attribute visible to false after some time.
Here is what I've got so far:
const withExpire = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
visible: props.visible,
};
}
render() {
const expire_in = this.props.expire_in || 3000;
if (this.state.visible) {
setTimeout(() => {
this.setState({ visible: false });
}, 1000);
}
return <WrappedComponent visible={this.state.visible} {...this.props} />;
}
}
I've checked that code inside if (this.state.visible) runs, but it doesn't change my visible attribute.
Can someone explain me what I'm missing?
EDIT
SOLUTION:
const withExpire = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
visible: props.visible,
};
this.timeoutID = null;
}
componentWillMount () {
const expire_in = this.props.expire_in || 3000;
if (this.state.visible) {
this.timeoutID = setTimeout(() => {
this.setState({ visible: false });
}, expire_in);
}
}
componentWillUnmount () {
if (this.timeoutID) {
window.clearTimeout(this.timeoutID);
}
}
render () {
return <WrappedComponent {...this.props} visible={this.state.visible} />;
}
}
You actually implemented the perfect example against HOC usage. You can't be sure about what's coming in from the outside as props.
In this example the external visible property through {...this.props} overrides the visbile={this.state.visible} property.
A quick fix (by swapping the order of property definitions):
...
return <WrappedComponent {...this.props} visible={this.state.visible} />;
...
Also, don't forget to properly handle the Timer. You should store any timers and if still active cancel them in componentWillUnmount. Otherwise a still running timer in an unmounted component might cause errors thrown around.
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);
}
I am rewriting some old ReactJS code, and got stuck fixing this error (the error repeats about 1700 times in the console, the DOM does not render at all):
Warning: setState(...): Cannot update during an existing state
transition (such as within render or another component's
constructor). Render methods should be a pure function of props and
state; constructor side-effects are an anti-pattern, but can be moved
to componentWillMount.
I am a Component that passes it's state down to a component that should render some controls. Based on the clicked controls, the state should change, and new controls should render.
So this is my Container component:
class TeaTimer extends Component {
constructor(props) {
super(props);
this.state = {
count: 120,
countdownStatus: 'started'
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus) {
switch (this.state.countdownStatus) {
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count:0});
}
}
}
componentWillUnmount() {
clearInterval(this.timer);
delete this.timer;
}
startTimer() {
this.timer = setInterval(() => {
let newCount = this.state.count -1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
if(newCount === 0) {
this.setState({countdownStatus: 'stopped'});
}
}, 1000)
}
handleStatusChange(newStatus) {
this.setState({ countdownStatus: newStatus });
}
render() {
let {count, countdownStatus} = this.state;
let renderStartStop = () => {
if (countdownStatus !== 'stopped') {
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
} else {
return <div>This will be the slider form</div>
}
};
return(
<div className={styles.container}>
<p>This is the TeaTimer component</p>
<Clock totalSeconds={count}/>
{renderStartStop()}
</div>
)
}
}
And this is my controls component:
class StartStop extends Component {
constructor(props) {
super(props);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={()=> this.onStatusChange('stopped')}>Reset</button>;
} else {
return <button onClick={()=> this.onStatusChange('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
I am sorry about the wall of text, but I really can;t figure out where the error is coming from - and therefor don't know which part of the code I can leave out.
I have tried implementing the solution found in a seemingly related question, but can't get it to work either.
I think you have a typo in this line:
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
It should be:
return <StartStop countdownStatus={countdownStatus} onStatusChange={() => this.handleStatusChange}/>
You seem to be calling the method handleStatusChange instead of passing it as a callback.
Your metods call each other so you must define two instance of your metods.
class StartStop extends Component {
constructor(props) {
super(props);
this.onStatusChangeReset=this.onStatusChange.bind(this);
this.onStatusChangeStart=this.onStatusChange.bind(this);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={this.onStatusChangeReset('stopped')}>Reset</button>;
} else {
return <button onClick={this.onStatusChangeStart('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
In this line in your return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/> gives the warning, the handleStatusChanged function is called on pressing a button which tries to change the state by setState keyword. whenever the state is changed render function is called again but in your case render function was in progress of returning while the render function is called again by setState keyword.