I am trying to pass in props to a component which works while using componentWillReceiveProps, but once the counter is done it calls clearInterval(this.intervalId);However once I change the input again the counter does not get initiated again. How can i pass the updated props back to the component?
Component code;
class Stopwatch extends Component {
constructor(props) {
super(props);
this.state = {
currentCount: this.props.counter,
hours: 0,
minutes: 0,
seconds: 0
}
}
componentWillMount() {
this.timer(this.props.counter);
}
timer() {
this.setState({
currentCount: this.state.currentCount - 1
})
const seconds = Math.floor(this.state.currentCount % 60);
const minutes = Math.floor((this.state.currentCount/60) % 60);
const hours = Math.floor((this.state.currentCount/3600) % 3600);
this.setState({hours, minutes, seconds});
if (this.state.currentCount < 1) {
clearInterval(this.intervalId);
}
}
componentDidMount() {
this.intervalId = setInterval(this.timer.bind(this), 1000);
}
leading0(num) {
return num < 10 ? '0' + num : num;
}
componentWillReceiveProps(nextProps){
if(nextProps.counter !== this.props.counter){
this.setState ({currentCount: nextProps.counter})
}
}
render() {
return (
<div>
<div>Hours {this.leading0(this.state.hours)}</div>
<div>Minutes {this.leading0(this.state.minutes)}</div>
<div>Seconds {this.leading0(this.state.seconds)}</div>
</div>
)
Main Code;
class App extends Component {
constructor(props) {
super(props);
this.state = {
deadline: 'December 25, 2018',
newDeadline: '',
counter: 75,
newCounter: ''
};
}
changeDeadline() {
this.setState({deadline: this.state.newDeadline});
}
changeNumber(e) {
this.setState({counter: this.state.newCounter});
}
render() {
return (
<div className='App'>
<div className='App-title'>Countdown to {this.state.deadline}</div>
<Clock
deadline={this.state.deadline}
/>
<Form inline>
<FormControl
className="Deadline-input"
placeholder='New Date'
onChange={event => this.setState({newDeadline: event.target.value})}
/>
<Button onClick={() => this.changeDeadline()}>Submit</Button>
</Form>
<div>Stopwatch From { this.state.counter } Seconds</div>
<Stopwatch
counter={this.state.counter}
/>
<Form inline>
<FormControl
className="Deadline-input"
placeholder='New Number'
onChange={event => this.setState({newCounter: event.target.value})}
/>
<Button onClick={() => this.changeNumber()}>Submit</Button>
</Form>
</div>
)
}
Thanks in Advance
componentDidMount function calls once, if you want to reset counter on props change, you should do it in componentWillReceiveProps function
class Stopwatch extends Component {
// ...
resetInterval() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
this.intervalId = setInterval(this.timer.bind(this), 1000);
}
componentWillReceiveProps(nextProps){
if(nextProps.counter !== this.props.counter){
this.setState ({currentCount: nextProps.counter})
// reset interval
this.resetInterval()
}
}
//...
}
Related
I am a newbie for react js. how to manage two Countdown timers first start and second is stop after 5-second interval second start and first stop.
it work for single Clock successful but add two clocks then first only start and not stop while second not start I don't know how do this ?.
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isActive: true
};
}
componentDidMount() {
this.intervalId = setInterval(() => {
this.randomCallObject();
}, 5000);
}
randomCallObject() {
this.setState({
Active: !this.state.isActive
});
}
render() {
let clock= {
time: 150,
isActive:this.state.isActive
}
let clock2= {
time: 100,
isActive:!this.state.isActive
}
return (
<div className="container">
<Clcok ClockData={clock}/>
<Clcok ClockData={clock2}/>
</div>
);
}
}
import React, { Component } from "react";
const TOTAL_MINUTES = 60;
export default class ClockComponent extends Component {
constructor(props) {
super(props);
this.state = {
time: props.ClockData.time,
isActive: props.ClockData.isActive
};
}
componentDidMount() {
const { isActive } = this.state;
if (isActive === true) {
this.intervalId = setInterval(() => {
const { time } = this.state;
if (time > 0) {
this.setState({
time: time - 1
});
}
}, 1000);
}
}
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
const { time } = this.state;
let minutes ="" + Math.floor((time % (TOTAL_MINUTES * TOTAL_MINUTES))/ TOTAL_MINUTES);
let seconds = "" + Math.floor(time % TOTAL_MINUTES);
if (isNaN(minutes) || isNaN(seconds)) {
return null;
}
if (minutes.length === 1) {
minutes = `0${minutes}`;
}
if (seconds.length === 1) {
seconds = `0${seconds}`;
}
return (
<div className="row">
<div className="col-md-1">
<div>
{minutes}:{seconds}
</div>
</div>
</div>
);
}
}
when clock data comes from props so take simple objects when isActive flag is true then clock timer on when isActive false then timer stop
To learn how to handle setInterval with React, I suggest you read the following blog post by Dan Abramov:
Making setInterval Declarative with React Hooks
In it, he explains how to use setInterval using React Hooks and also how to do it using a class component. On the post, there is also a link to a CodeSandbox example where you can see it in action.
What I did was create another CodeSandbox where you can see how you could apply this example to run multiple timers:
https://codesandbox.io/embed/timers-l6me1
I've used React Hooks in the example because they don't require a lot of code.
I hope it helps.
edit #1
Here is an example of a Counter component taken directly from the mentioned article, and adapted to fit the latter example.
class Counter extends React.Component {
state = {
count: 0,
delay: 1000,
isRunning: true
};
constructor(props) {
super(props);
this.state = { ...this.state, ...props };
}
componentDidMount() {
this.interval = setInterval(this.tick, this.state.delay);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.delay !== this.state.delay) {
this.startInterval();
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
startInterval = () => {
clearInterval(this.interval);
this.interval = setInterval(this.tick, this.state.delay);
console.log(this.interval);
};
tick = () => {
this.setState({
count: this.state.count + 1
});
};
handleDelayChange = e => {
this.setState({ delay: Number(e.target.value) });
};
toggleCounter = () => {
console.log(this.state.isRunning);
if (this.state.isRunning) {
clearInterval(this.interval);
} else {
this.startInterval(this.state.delay);
}
this.setState({
count: 0,
isRunning: !this.state.isRunning
});
};
render() {
const {
state: { isRunning, delay, count },
toggleCounter,
handleDelayChange
} = this;
return (
<>
<h1>{count}</h1>
<input value={delay} onChange={handleDelayChange} />
<button onClick={toggleCounter}>{isRunning ? "stop" : "start"}</button>
</>
);
}
}
I am building my first React app and am doing an app that people can use to split their bills with others. The app is deployed on heroku through this link http://split-calc.herokuapp.com.
The problem I am having is the following:
Lets say you start by typing in 100 for Meal total, 20 for Shared Items, and 10 for tax and tip, resulting in a grand total of 140.
Next, you click the 'Add Person' button, which renders a Person component. Here you will see an input for 'Person subtotal'. Shared will automatically start at 20 because you are so far only splitting the bill with yourself. As you add a value to 'Person Subtotal', the tip, tax and total with dynamically update. Now the problem lies when you click 'Add Person' once again. Person #2 now has a Shard value of 10, which is correct because now the 'Shared Items', which was $20, is now being split by two people (20/2 = 10). The problem is, Person #1 still has a Shared value of $20. Now if you click 'Add Person' once again, the third person will have the correct shared value, but now person #1 and #2 have outdated values. How can I update these dynamically?
Below is all of my code so far.
function Header() {
return (
<header>Split Bill Calculator</header>
)
}
function AddPerson(props) {
return (
<div className='button'>
<button className='addPerson' onClick={props.onClicked}>Add Person</button>
</div>
);
}
function PersonList(props) {
return (
<div>
{props.persons.map((person, index) => (
<span key={index}>{person}</span>
))}
</div>
);
};
class Details extends React.Component {
constructor(props) {
super(props);
this.state = {
meal_total: 0,
shared_items: 0,
tax: 0,
tip: 0,
persons: [],
counter: 1,
};
this.handleClick = this.handleClick.bind(this)
this.handleMealChange = this.handleMealChange.bind(this)
this.handleSharedItemsChange = this.handleSharedItemsChange.bind(this)
this.handleTaxChange = this.handleTaxChange.bind(this)
this.handleTipChange = this.handleTipChange.bind(this)
}
handleMealChange = event => {
this.setState({
meal_total: event.target.value,
});
};
handleSharedItemsChange = event => {
this.setState({
shared_items: event.target.value,
});
};
handleTaxChange = event => {
this.setState({
tax: event.target.value,
});
};
handleTipChange = event => {
this.setState({
tip: event.target.value,
});
};
handleClick = () => {
let counter = this.state.counter + 1
let addPerson = this.state.persons.concat(this.renderPerson())
this.setState({
persons: addPerson,
counter: counter,
});
}
renderPerson = () => {
return (
<Person
person_tax = {this.state.tax}
person_tip = {this.state.tip}
shared_items = {this.state.shared_items}
counter = {this.state.counter}
/>
)
}
renderAddPerson = () => {
return (
<AddPerson
onClicked= {() => this.handleClick()}
/>
);
}
render() {
let grand_total = parseFloat(this.state.meal_total) + ((parseFloat(this.state.meal_total)) * (parseFloat(this.state.tax)/100)) + ((parseFloat(this.state.meal_total)) * (parseFloat(this.state.tip)/100));
return (
<div className='details'>
<div className='order-total'>
<form>
<label htmlFor='meal'>Meal subtotal: ($)</label><br></br>
<input name='meal' placeholder={this.state.meal_total} onChange={this.handleMealChange}></input><br></br>
<label htmlFor='meal'>Shared items: ($)</label><br></br>
<input name='meal' placeholder={this.state.shared_items} onChange={this.handleSharedItemsChange}></input><br></br>
<label htmlFor='tax'>Tax: (%)</label><br></br>
<input name='tax' placeholder={this.state.tax} onChange={this.handleTaxChange}></input><br></br>
<label htmlFor='tip'>Tip: (%)</label><br></br>
<input name='tip' placeholder={this.state.tip} onChange={this.handleTipChange}></input><br></br>
<label htmlFor='total'>Grand Total: ($)</label><br></br>
<input name='total' value={grand_total.toFixed(2)} readOnly></input><br></br>
</form>
</div>
<PersonList persons={this.state.persons} />
{this.renderAddPerson()}
</div>
);
}
}
class Person extends React.Component {
constructor(props) {
super(props);
this.state = {
person_tax: props.person_tax,
person_tip: props.person_tip,
person_meal_subtotal: 0,
shared_items: props.shared_items,
}
}
handlePersonSubtotal = event => {
this.setState({
person_meal_subtotal: event.target.value
});
};
render() {
let person_total = parseFloat(this.state.person_meal_subtotal) + parseFloat(this.props.shared_items) + ((parseFloat(this.state.person_meal_subtotal)) * (parseFloat(this.state.person_tax)/100)) + ((parseFloat(this.state.person_meal_subtotal)) * (parseFloat(this.state.person_tip)/100));
let shared_items = this.state.shared_items / this.props.counter;
return (
<div className='person'>
<div className='total-details'>
<h3>Person {this.props.number} </h3>
<form>
<label htmlFor='person-meal'>Personal Subtotal: $ </label>
<input name='person-meal' value={this.state.person_meal_subtotal} onChange={this.handlePersonSubtotal}></input>
</form>
</div>
<div className='breakdown'>
<h3>Should Pay</h3>
<div className='person-details'>
<p>Shared: ${(parseFloat(shared_items)).toFixed(2)}</p>
<p>Tax: ${((parseFloat(this.state.person_tax)/100) * parseFloat(this.state.person_meal_subtotal)).toFixed(2)}</p>
<p>Tip: ${((parseFloat(this.state.person_tip)/100) * parseFloat(this.state.person_meal_subtotal)).toFixed(2)}</p>
<p>Total: ${person_total.toFixed(2)}</p>
</div>
</div>
</div>
)
}
}
class Calculator extends React.Component {
render() {
return (
<div>
<Header />
<Details/>
</div>
)
}
}
ReactDOM.render(<Calculator />, document.getElementById('root'));
You might want to do it later
handleClick = () => {
let counter = this.state.counter + 1
let addPerson = this.state.persons.concat(this.renderPerson())
this.setState({
persons: addPerson,
counter: counter,
});
}
instead of this
try this
handleClick = () => {
let counter = this.state.counter + 1
this.setState({
persons: addPerson,
counter: counter,
}, () => {
let addPerson = this.state.persons.concat(this.renderPerson())
this.setState({
persons: addPerson
});
});
}
https://www.npmjs.com/package/nouislider-react
can't insert value into slider
class KeyboardSlider extends React.Component {
state = { ref: null };
changeByRef = () => {
const { ref } = this.state;
if (ref && ref.noUiSlider) {
ref.noUiSlider.set(20);
}
};
render() {
return (
<div>
<button onClick={this.changeByRef}>Change with ref</button>
<Nouislider
instanceRef={instance => {
if (instance && !ref) {
this.setState({ ref: instance });
}
}}
start={0}
range={{
min: 0,
max: 100
}}
/>
</div>
);
}
}
this code does not work gives an error
Line 665: 'ref' is not defined no-undef
....................................................................................................
try this code i just refactor you are using the ref in wrong way.
you can also use input and change the value by updating the start prop on Slider.
class KeyboardSlider extends React.Component {
constructor(props){
super(props);
this.ref = React.CreateRef();
}
changeByRef = () => {
if (this.ref && this.ref.noUiSlider) {
this.ref.noUiSlider.set(20);
}
};
render() {
return (
<div>
<button onClick={this.changeByRef}>Change with ref</button>
<Nouislider
instanceRef={instance => {
if (instance && !ref) {
this.setState({ ref: instance });
}
}}
start={0}
range={{
min: 0,
max: 100
}}
/>
</div>
);
}
I'm failing to understand how to update the content inside my render function based on logic from inside of a function. Do I have to return a whole new render function in order to do so? If so, that seems counter intuitive with React's framework of state & props and such...
Here's what I've tried:
tick() {
this.setState(prevState => ({
minutes: prevState.seconds + 1,
}));
if(this.state.minutes > this.state.targetGoal){
console.log("NONONONONO");
return (<div>SOMETHING NEW</div>); //update content inside render()
}
}
async componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div style={divStyle}>
<div>Your Second Count: {this.state.seconds}</div>
<habitlab-logo-v2></habitlab-logo-v2>
<br/>
<close-tab-button></close-tab-button>
</div>
);
}
}
There are a few issues.
As #Jayce444 has pointed out, you need to change a state to trigger render to re-render.
So create a new flag (say isOvertime) to trigger the render to fire.
tick() {
this.setState(
prevState => ({
seconds: prevState.seconds + 1
}),
() => {
if (this.state.seconds > this.state.targetGoal) {
console.log("NONONONONO");
// return <div>SOMETHING NEW</div>; //update content inside render()
this.setState({ isOvertime: true });
}
}
);
}
And in the render, you show a component depending on the isOvertime.
render() {
const { isOvertime, seconds } = this.state;
return (
<div>
{isOvertime ? (
<div>Time Over Man!</div>
) : (
<div>Your Second Count: {seconds}</div>
)}
<input
type="number"
value={this.state.targetGoal}
onChange={e => this.setState({ targetGoal: e.target.value })}
/>
</div>
);
}
Here is the full source. (demo availabe on CodeSandBox).
Output
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
interval: 0,
seconds: 0,
targetGoal: 4,
isOvertime: false
};
tick() {
this.setState(
prevState => ({
seconds: prevState.seconds + 1
}),
() => {
if (this.state.seconds > this.state.targetGoal) {
console.log("NONONONONO");
// return <div>SOMETHING NEW</div>; //update content inside render()
this.setState({ isOvertime: true });
}
}
);
}
componentDidMount() {
const interval = setInterval(() => this.tick(), 1000);
this.setState({ interval });
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { isOvertime, seconds } = this.state;
return (
<div>
{isOvertime ? (
<div>Time Over Man!</div>
) : (
<div>Your Second Count: {seconds}</div>
)}
<input
type="number"
value={this.state.targetGoal}
onChange={e => this.setState({ targetGoal: e.target.value })}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I am trying to use redux-form with react-widget Multiselect this example:
var Multiselect = ReactWidgets.Multiselect
, people = listOfPeople();
var Example = React.createClass({
getInitialState() {
return { value: people.slice(0,2) };
},
_create(name){
var tag = { name, id: people.length + 1 }
var value = this.state.value.concat(tag)
// add new tag to the data list
people.push(tag)
//add new tag to the list of values
this.setState({ value })
},
render(){
// create a tag object
return (
<Multiselect data={people}
value={this.state.value}
textField="name"
onCreate={this._create}
onChange={value => this.setState({ value })}/>
)
}
});
ReactDOM.render(<Example/>, mountNode);
Below is a code snippet for a parent component which makes usage of redux-form (EditVideo component) component (please look at the comments in onSubmit method):
class VideoEdit extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (values) => {
console.log(values.categories) // always returns initialValues for categories, new values not adding
}
render() {
const { loading, videoEdit, categories } = this.props;
if (loading) {
return (
<div>{ /* loading... */}</div>
);
} else {
return (
<div>
<h2>Edit: {videoEdit.title}</h2>
<EditVideo
onSubmit={this.onSubmit}
initialValues={videoEdit}
categories={categories}
/>
</div>
);
}
}
}
And here is a code snippet of redux-form component with react-widget Multiselect component:
class CategoryWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defValue,
extData: this.props.data
}
this._create = this._create.bind(this);
}
_create(name) {
var tag = { name, id: this.state.extData.length + 100 + 1 }
var value = this.state.value.concat(tag)
var extData = this.state.extData.concat(tag)
this.setState({
extData,
value
})
}
render() {
return (
<Multiselect
{...this.props.input}
data={this.state.extData}
onBlur={() => this.props.input.onBlur()}
value={this.state.value || []}
valueField="id"
textField="name"
onCreate={this._create}
onChange={value => this.setState({ value })}
/>
)
}
}
const EditVideoForm = (props) => {
const { handleSubmit, submitting, onSubmit, categories, initialValues, defBook } = props;
return (
<Form name="ytvideo" onSubmit={handleSubmit(onSubmit)}>
<div>
<Field
name="categories"
component={CategoryWidget}
data={categories}
defValue={initialValues.categories}
/>
</div>
<br />
<Button color="primary" type="submit" disabled={submitting}>
Submit
</Button>
</Form>
);
};
export default reduxForm({
form: 'videoEdit',
enableReinitialize: true
})(EditVideoForm);
The Multiselect widget works as expected, yet the form on submit always returns the same initial values for categories.
I believe the problem lays in the fact that CategoryWidget is a class base component? If so, what is a way to make it work?
Here is what I have done for my Multiselect at the end:
class CategoryWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defValue,
extData: this.props.data
}
this._create = this._create.bind(this);
}
_create(name) {
var tag = { name, id: this.state.extData.length + 100 + 1 }
var value = this.state.value.concat(tag)
var extData = this.state.extData.concat(tag)
this.setState({
extData,
value
})
}
componentDidUpdate() {
let { onChange } = this.props.input
onChange(this.state.value)
}
handleOnChange(value) {
this.setState({ value })
}
render() {
const input = this.props.input
return (
<Multiselect
{...input}
data={this.state.extData}
onBlur={() => input.onBlur()}
value={this.state.value || []}
valueField="id"
textField="name"
onCreate={this._create}
onChange={value => this.handleOnChange(value)}
/>
)
}
}