I have a component that will start counting down from 5 mins when a timestamp (obsTakenTime) is received via props. When the countdown gets <=0, I render ‘Overdue’. At this point I need to clear interval which I think I've done, the issues is when if, I refresh the page the obstimeleft should remain overdue but the countdown automatically starts from 59 mins because the value of nextObservationTime becomes 59min and this.state.obsTimeleft becomes undefined even thought the value of timestamp obsTakenTime is the same. I've looked at other similar threads on SO but I couldn't get mine to work. Any help is appreciated.
similar post - Clear interval in React class
Countdown component
export default class ObservationCountDown extends React.Component {
constructor(props) {
super(props);
this.state = {
obsTimeleft: undefined
};
this.countDown = this.countDown.bind(this);
this.startCounter = this.startCounter.bind(this);
this.countDownInterval = null;
}
countDown() {
const { obsTakenTime} = this.props; //when last obs was taken by the user in ms
const nextDueTimeForObs = moment(obsTakenTime).add(5, 'minutes');
const nextObservationTime = Number(nextDueTimeForObs.subtract(moment.now()).format('m'));
const timeToDisable = 2; // disable buttons time
this.setState({ obsTimeleft: nextObservationTime + ' min' }, () => {
if (nextObservationTime <= Number(timeToDisable)) {
this.props.disablePatientUpdate();
}
if (nextObservationTime <= 0) {
clearInterval(this.countDownInterval); // doesn't work here
this.setState({ obsTimeleft: 'Overdue' }, () => {
if(this.state.obsTimeleft === 'Overdue'){
clearInterval(this.countDownInterval); // doesn't work here
}
});
}
});
}
componentDidMount() {
this.startCounter();
}
startCounter() {
this.countDownInterval = setInterval(this.countDown, 1000);
}
componentDidUpdate(prevProps){
if(this.props.patient.timestamp !== prevProps.patient.timestamp){
this.startCountdown();
}
}
componentWillUnmount(){
clearInterval(this.countDownInterval);
}
render() {
const { obsTimeleft } = this.state;
return (
<>
{(obsTimeleft && obsTimeleft === 'Overdue') ?
<div className="text-danger">
<strong>{obsTimeleft}</strong>
</div> :
<div>
<strong>{.obsTimeleft}</strong>
</div>}
</>
);
}
}
another version of countDown() that I tried and didn't work
countDown() {
const { obsTakenTime } = this.props; // obs duration - when last obs was taken by the user in min
const nextDueTimeForObs = moment(obsTakenTime).add(2, 'minutes');
const nextObservationTime = Number(nextDueTimeForObs.subtract(moment.now()).format('m'));
console.log('nextObservationTime', nextObservationTime);
this.setState({ obsTimeleft: nextObservationTime + ' min' })
if (nextObservationTime <= 0) {
this.setState({ obsTimeleft: 'Overdue' }, () => {
if(this.state.obsTimeleft === 'Overdue') {
clearInterval(this.countDownInterval);
}
});
this.props.enablePatientUpdate();
this.props.resetPatient(patient);
}
}
Related
This is the updated code now. Let me know if something is not correct as I am able to compile but the issue still persists
constructor(props) {
super(props);
this.polyglot = PolyglotFactory.getPolyglot(props.pageLang);
this.state = {
otherInvestorSubtype: props.otherInvestorSubtype,
};
}
shouldRenderOtherSubtype = () => this.props.otherInvestorSubtype === OTHER_INVESTOR_SUBTYPE;
shouldRenderSubtype = () => {
const { investorTypeOptions, investorType } = this.props;
const investorTypeOption = investorTypeOptions.find(({ value }) => value === investorType);
return investorTypeOption !== undefined && investorTypeOption.subtypes.length > 0;
}
handleOtherInvestorSubtypeChange = (e) => {
this.setState({
otherInvestorSubtype: e.target.value,
});
this.props.handleOtherInvestorSubtypeChange();
}
renderSelectOtherSubtype = () => {
const { handleOtherInvestorSubtypeChange,
showError, registerChildValidator, pageLang } = this.props;
const { otherInvestorSubtype } = this.state;
return (
<ValidatedText
name="investor_subtype_other"
value={otherInvestorSubtype}
onChange={this.handleOtherInvestorSubtypeChange}
showError={showError}
registerValidation={registerChildValidator}
validation={validation(this.polyglot.t('inputs.investorTypes'), pageLang, rules.required())}
required
/>
);
}
This is the only information I have got for this. Let me know if something is missing.
It seems like you've set the textbox value as otherInvestorSubtype which is provided by the props. This way, the component (so that the textbox value) is not updated on textbox value change. You need to store the otherInvestorSubtype value provided by props in the InvestorType component's state as the following, in order to update the InvestorType component every time the user types something:
constructor(props) {
...
this.state {
otherInvestorSubtype: props.otherInvestorSubtype
}
}
change the renderSelectOtherSubtype method as the following:
renderSelectOtherSubtype = () => {
const { handleOtherInvestorSubtypeChange, showError, registerChildValidator, pageLang } = this.props;
const { otherInvestorSubtype } = this.state
return (
<ValidatedText
...
value={ otherInvestorSubtype }
onChange={ this.handleOtherInvestorSubtypeChange }
...
/>
);
}
and finally handle the textbox change on this component:
handleOtherInvestorSubtypeChange = (e) => {
this.setState({
otherInvestorSubtype: e.target.value
});
this.props.handleOtherInvestorSubtypeChange();
}
Hope this helps, and sorry if I have any typos.
The number would increased by 1 when I pushed the button.
However, after refreshing the screen, the number shows 「0」.
I want to show the number where it left off before refreshing.
Where should I fix to store the value in AsyncStorege correctly?
Could you give some advice please?
export default class ApplauseButton extends Component {
constructor(props) {
super(props);
this.state = {
applause: 0,
};
}
componentDidMount = () => {
const applauseCount = parseInt(AsyncStorage.getItem('applause'),10);
this.setState({applaused:applauseCount});
};
handlClick() {
const countapplause = this.state.applause + 1;
AsyncStorage.setItem('applause', countapplause.toString()).then(() => {
this.setState({ applause: this.state.applause + 1});
});
};
render() {
return (
<View style={styles.container}>
<Button title="👋"
onPress={() => {
this.handlClick()
}} />
<Text style={styles.count}>
{this.state.applause}/
</Text>
</View>
);
}
}
I suppose there are two things you should change:
1.Set the state when you get the count from the AsyncStorage.
2.you are setting the previous value in AsyncStorage instead store the incremented value.
componentDidMount = () => {
getcount();
};
getcount = async () => {
let count = '';
try {
count = await AsyncStorage.getItem('applause') || '0';
count = parseInt(count,10);
this.setState({applause:count})
} catch (error) {
// Error retrieving data
console.log(error.message);
}
}
handlClick= async ()=> {
const count = this.state.applause + 1;
try{
await AsyncStorage.setItem('applause', count.toString());
this.setState({ applause: count});
}
catch(error){
console.log(error.message);
}
};
I'd like to fire interval after backend data will come in as a prop.
Take a look at this chunk of code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAdvert } from "../../actions/advert";
import './_advert.scss';
class Advert extends Component {
state = { counter: 0 };
componentDidMount() {
const { getAdvert } = this.props;
getAdvert();
}
componentWillReceiveProps(nextProps) {
const { counter } = this.state;
this.bannerInterval = setInterval(() => {
this.setState({ counter: counter === Object.keys(nextProps.banners).length - 1 ? 0 : counter + 1 });
}, 1000)
}
render() {
const { banners } = this.props;
const { counter } = this.state;
return (
<div className="advert__container">
<img src={banners[counter] && banners[counter].image_url} alt="advert" />
</div>
);
}
}
const mapStateToProps = ({ banners }) => {
return { banners };
};
const mapDispatchToProps = (dispatch) => {
return {
getAdvert: () => dispatch(getAdvert())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Advert);
So as you can see I tried to run it within componentWillReceiveProps method as I thought it might be proper place to be dependent on incoming props. But that won't work. I will run only once and interval will be repeating the same value.
Thanks for helping!
ComponentWillReceive props is extremely dangerous used in this way. You are istantiating a new interval every time a props is received without storing and canceling the previous one.
Also is not clear how do you increment counter, as I see the increment in your ternary will not increase the counter value incrementaly.
// This is an example, the lifeCylce callback can be componentWillReceive props
componentDidMount() {
const intervalId = setInterval(this.timer, 1000);
// store intervalId in the state so it can be accessed later:
this.setState({intervalId: intervalId});
}
componentWillUnmount() {
// use intervalId from the state to clear the interval
clearInterval(this.state.intervalId);
}
timer = () => {
// setState method is used to update the state with correct binding
this.setState({ currentCount: this.state.currentCount -1 });
}
I am using this to create a 3D interactive view for a product: https://github.com/aldrinc/React360. The code in question is:
import React, { Component } from "react";
import "./React360.css";
// You can play with this to adjust the sensitivity
// higher values make mouse less sensitive
const pixelsPerDegree = 3;
class React360 extends Component {
static defaultProps = { dir: 'awair-360', numImages: 55 };
state = {
dragging: false,
imageIndex: 0,
dragStartIndex: 0
};
componentDidMount = () => {
document.addEventListener("mousemove", this.handleMouseMove, false);
document.addEventListener("mouseup", this.handleMouseUp, false);
};
componentWillUnmount = () => {
document.removeEventListener("mousemove", this.handleMouseMove, false);
document.removeEventListener("mouseup", this.handleMouseUp, false);
};
handleMouseDown = e => {
e.persist();
this.setState(state => ({
dragging: true,
dragStart: e.screenX,
dragStartIndex: state.imageIndex
}));
};
handleMouseUp = () => {
this.setState({ dragging: false });
};
updateImageIndex = currentPosition => {
let numImages = this.props.numImages;
const pixelsPerImage = pixelsPerDegree * (360 / numImages);
const { dragStart, imageIndex, dragStartIndex } = this.state;
// pixels moved
let dx = (currentPosition - dragStart) / pixelsPerImage;
let index = Math.floor(dx) % numImages;
if (index < 0) {
index = numImages + index - 1;
}
index = (index + dragStartIndex) % numImages;
// console.log(index, dragStartIndex, numImages)
if (index !== imageIndex) {
this.setState({ imageIndex: index });
}
};
handleMouseMove = e => {
if (this.state.dragging) {
this.updateImageIndex(e.screenX);
}
};
preventDragHandler = e => {
e.preventDefault();
};
renderImage = () => {
const { imageIndex } = this.state;
if (isNaN(imageIndex)) {
this.setState({imageIndex: 0})
return
}
return (
<div className="react360">
<img
className="react-360-img"
alt=""
src={require(`./${this.props.dir}/${imageIndex}.jpg`)}
/>
</div>
);
};
render = () => {
return (
<div
className="react-360-img"
onMouseDown={this.handleMouseDown}
onDragStart={this.preventDragHandler}
>
{this.renderImage()}
</div>
);
};
}
export default React360;
I am running into an issue where hardcoding the variable for the number of images (numImages) results in proper function but when I set the number of images as a prop let numImages = this.props.numImages; my image index sometimes goes to NaN. I've made a hacky workaround by setting the imageIndex state to 0 if it is NaN but I would like to resolve this issue correctly by understanding what exactly is different with setting a variable using props vs hardcoding.
I'm a beginner for ReactJS, just started learning and started writing code for guessing numbers but the guess count shows different values. {this.state.attempts} holds the no. of attempts it took the user to find the answer to show the correct value. But {this.state.result} shows the result on each click but if user finds the answer it shows the previous state. I'm wondering how this happens. Is that because it's not under render()?
import React, { Component } from 'react';
export default class NoLifeCycComps extends Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.checkValue = this.checkValue.bind(this);
this.updateInput = this.updateInput.bind(this);
}
randNum(){
return Math.floor((Math.random() * 100) + 1);
}
getInitialState(){
return {
num: this.randNum(),
inputValue: '',
attempts: 0,
result: '',
reset : false
}
}
reset() {
this.setState(this.getInitialState());
}
checkValue() {
this.setState((prevState) => {
return { attempts: prevState.attempts + 1 }
});
if (this.state.inputValue > this.state.num) {
this.state.result = "higher";
} else if (this.state.inputValue < this.state.num) {
this.state.result = "lesser";
} else if (this.state.inputValue == this.state.num) {
this.state.result = "you found it on " + this.state.attempts + "attempt(s)";
this.state.reset = true;
}
}
updateInput(e) {
this.setState({ inputValue: e.target.value })
}
render() {
return (
<div className = "numberGuess">
<h3> Guess the number </h3>
<input type = "text" value = { this.state.inputValue } onChange = { this.updateInput }/>
{this.state.reset ? <button onClick = { () => this.reset() }> start again! </button> : <button onClick = { () => this.checkValue() }> click </button>}
No.of Attempts took: { this.state.attempts } <br/>
<span> { this.state.result } </span>
</div>
);
}
}
setState is a async function. Next statement of setState may not have updated state value. Also I found mutation state update in your code. Please avoid mutation update. You should update all state using setState
Example
checkValue() {
let {
attempts,
inputValue,
num,
result,
reset
} = this.state;
attempts++;
if (inputValue > num) {
result = "higher";
} else if (inputValue < num) {
result = "lesser";
} else if (inputValue == num) {
result = "you found it on " + attempts + "attempt(s)";
reset = true;
}
this.setState({
attempts,
result,
reset
});
}
In this example we storing existing state value in variables and doing operation on that. At the end of calculation we are updating state once.