I'm building a sort of clock with React that has an option to increment or decrement a number (25 as default) in one component, and in another component it updates the timer (25:00 since we start at 25) to whatever the number is incremented or decremented to.
I have two components (Session and Clock) successfully performing their own actions, however I'm stumped as to how I can get the counter (Session component) to update the state of the timer in the Clock component. More specifically, I've been toying with this.props.minutes to no avail.
Question: How can I go about sharing the this.state.minutes property among components? Thank you in advance. I'm still a total beginner at React.
Session:
const Session = React.createClass({
getInitialState: function() {
return {
minutes: 25,
seconds: 0
};
},
increment: function() {
this.setState({ minutes: this.state.minutes + 1 });
},
decrement: function() {
this.setState({ minutes: this.state.minutes - 1 });
},
timeToString: function(time) {
return time + ':00';
},
render: function() {
return (
<section>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
{this.state.minutes}
<Clock/>
</section>
);
}
});
module.exports = Session;
Clock:
const Clock = React.createClass({
getInitialState: function() {
return { currentCount: 10 };
},
startTimer: function() {
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},
pauseTimer: function() {
clearInterval(this.state.intervalId);
this.setState({ intervalId: this.state.currentCount });
},
timer: function() {
var newCount = this.state.currentCount - 1;
if (newCount >= 0) {
this.setState({ currentCount: newCount });
} else {
clearInterval(this.state.intervalId);
}
},
render: function() {
return (
<section>
<button onClick={this.startTimer}>Start</button>
<button onClick={this.pauseTimer}>Pause</button>
{this.state.currentCount}
</section>
);
}
});
module.exports = Clock;
You need to pass in the state from Session to Clock like so:
<Clock time={this.state.minutes} /> in your Session component
Then the 'state' is now available to your Clock component as this.props.time
or whatever you call it in the above code.
The moral of the story is that state passed down to from a parent component to a child component is done so using props
Relevant Docs:
https://facebook.github.io/react/docs/multiple-components.html
Edit: another key link in the docs:
https://facebook.github.io/react/tips/communicate-between-components.html
Related
I have the following code for updating the number of seconds elapsed and displaying it:
<template>
<div>
{{timerValue}}
</div>
</template>
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: ""
}
},
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(function() {
seconds++;
})
}
};
</script>
However the page always displays
0
What am I doing wrong?
https://codesandbox.io/s/still-cache-1mdgr6?file=/src/App.vue
Maybe like following snippet:
const app = Vue.createApp({
data() {
return {
timerValue: 0 // set value default is 0
}
},
created() {
setInterval(() => {
this.timerValue++;
}, 1000)
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div>
{{timerValue}}
</div>
</div>
You shuold increment this.timerValue instead seconds
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(function() {
this.timerValue++;
}.bind(this))
}
Or with arrow functions
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(() => {
this.timerValue++;
})
}
I have 3 points for you to pay attention to
In the setInterval function you only change the seconds variable, not this.timerValue at all. So page always shows 0.
The setInterval function doesn't have interval argument yet, so it won't run every second.
Also the initialization value is not clear, I think you should understand the logic of the program and spent a little time for understand what you written.
<template>
<div>
{{timerValue}}
</div>
</template>
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: 0,
}
},
mounted() { // use mounted much better than created in this case
setInterval(() => {
this.timerValue++;
}, 1000)
}
};
</script>
refs: https://www.w3schools.com/jsref/met_win_setinterval.asp
that's why i always tell people to learn language logic or at least javascript before learning something else
you need to update the react value because javascript doesn't have an api that allows memory cell assignment
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: 0 // set default value is number
}
},
created() {
setInterval(() => {
this.timerValue++;
}, 1_000 /* delay 1s */)
}
};
</script>
I found two things wrong about my code:
Using this inside a function(). An arrow function should be used, like this:
setInterval(() => {}
this.timerValue = seconds is not enough to make Vue update timerValue when seconds is updated. Change of seconds doesn't trigger change of timerValue.
It can be solved by using computed property:
computed: {
timerValue: function() {
return this.seconds;
}
},
So the whole code would look like this:
https://codesandbox.io/s/festive-cannon-8qvpn9?file=/src/App.vue
<template>
<div>
{{ timerValue }}
</div>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {
seconds: 0,
};
},
created() {
setInterval(() => {
this.seconds++;
}, 1000);
},
computed: {
timerValue: function () {
return this.seconds;
},
},
};
</script>
I'm trying to implement timer that counts the next event from the JSON API. Unfortunately the API does not support filter so I have to process it on the component
here's the code:
constructor(props) {
super(props);
this.scrollViewRef = React.createRef();
this.state = {
agendaVisible: false,
scrollOffset: null,
markedDates: {},
dateString: Date(),
agendaItems: {},
nextEvent: {
start: 99999999999,
name: "",
},
isProcessing: true,
};
this.closeModal = this.closeModal.bind(this);
this.openModal = this.openModal.bind(this);
}
setMarkedDates = () => {
const { events } = this.props;
if (events !== undefined) {
Object.keys(events).map((key) => {
let now = Date.now();
let difference = events[key].start * 1000 - now;
let a = new Date(events[key].start * 1000);
let date = `${a.getUTCFullYear()}-${this.str_pad(
a.getUTCMonth() + 1
)}-${this.str_pad(a.getUTCDate())}`;
if (difference > 0 && difference < this.state.nextEvent.start) {
this.setState({
nextEvent: {
start: events[key].start * 1000,
name: events[key].name,
},
});
console.log("Goes in here: " + events[key].start);
}
}
// Set Marked dates on calendar and agenda Items
this.setState(({ markedDates, agendaItems }) => ({
markedDates: {
...markedDates,
[date]: {
marked: true,
selectedColor: "blue",
activeOpacity: 0,
dotColor: "blue",
},
},
agendaItems: {
...agendaItems,
[date]: [
{
name: events[key].name,
height: 100,
},
],
},
}));
});
this.setState({ isFetching: false });
}
};
Then I passed in the nextEvent to my EventTimer component in the render
{!this.state.isFetching && (
<EventTimer
nextEvent={this.state.nextEvent}
nextEventStart={this.state.nextEventStart}
/>
)}
Now the problem is, whenever I called {this.props.nextEvent.start} in the render, it works, however I need to set it up the state of eventTimer for static endTime to calculate the difference every interval, I put the code on componentWillReceiveProps but I never get the updated props? it still stays at 9999999999, what happened?
If it doesn't receive the props then which part of the lifecycle received the props? Because it works when I tried to render it. I'm not sure where should I update the state from the props anymore.
Please help, Thanks!
componentWillReceiveProps(nextProps) {
this.setState({
endTime: Date.now() + this.props.nextEvent.start,
});
this.resetTimer();
}
resetTimer = () => {
if (this.interval) clearInterval(this.interval);
this.interval = setInterval(
this.setTimeRemaining,
this.timeRemaining % 1000
);
};
You are referring to the initial props in the componentWillReciveProps.
this.setState({
// this.props refers to the current props.
// endTime: Date.now() + this.props.nextEvent.start,
endTime: Date.now() + nextProps.nextEvent.start,
});
While that should fix the bug you are facing. You should try to not use this lifecycle method and instead depend on a calculated value from the parent component.
The componentWillReciveProps will be deprecated in the next major version. You can read a bit more about this in the react docs.
I have got problem with my react component which renders form. Basically, when I enter countdown page, the form just doesn't work (by that I mean it doesnt act at all, I write for example 123 which is 2 min and 3 seconds and nothing happens, just nothing). But, for example, if I go on to main page and back to countdown page, it works. I have noticed that when entering this page the first time, componentWillMount works, but componentDidMount doesn't (it won't console.log the message).
Link to heroku: http://fathomless-lowlands-79063.herokuapp.com/?#/countdown?_k=mj1on6
CountdownForm.jsx
var React = require('react');
var CountdownForm = React.createClass({
onSubmit: function (e) {
e.preventDefault();
var strSeconds = this.refs.seconds.value;
if (strSeconds.match(/^[0-9]*$/)){
this.refs.seconds.value = '';
this.props.onSetCountdown(parseInt(strSeconds, 10));
}
},
render: function () {
return(
<div>
<form ref="form" onSubmit={this.onSubmit} className="countdown-form">
<input type="text" placeholder="Enter time in seconds" ref="seconds" />
<button className="button expanded">Start
</button>
</form>
</div>
);
}
});
module.exports = CountdownForm;
Countdown.jsx
var React = require('react');
var Clock = require('Clock');
var CountdownForm = require('CountdownForm');
var Controls = require('Controls');
var Countdown = React.createClass({
getInitialState: function () {
return {
count: 0,
countdownStatus: 'stopped'
};
},
componentDidUpdate: function (prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus)
{
switch (this.state.countdownStatus){
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count: 0})
case 'paused':
clearInterval(this.timer)
this.timer = undefined;
break;
}
}
},
componentDidMount: function() {
console.log("componentDidMount");
},
componentWillMount: function () {
console.log("componentWillMount");
},
componentWillUnmount: function () {
console.log('componentDidUnmount');
},
startTimer: function () {
this.timer = setInterval(() => {
var newCount = this.state.count - 1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
}, 1000);
},
handleSetCountdown: function (seconds){
this.setState({
count: seconds,
countdownStatus: 'started'
});
},
handleStatusChange: function (newStatus) {
this.setState({
countdownStatus: newStatus
});
},
render: function () {
var {count, countdownStatus} = this.state;
var renderControlArea = () => {
if (countdownStatus !== 'stopped') {
return <Controls countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange} />
} else {
return <CountdownForm onSetCountdown={this.handleSetCountdown} />
}
};
return(
<div>
<Clock totalSeconds={count} />
{renderControlArea()}
</div>
);
}
});
module.exports = Countdown;
I have solved the problem. Main issue was error: "Uncaught Error: Stateless function components cannot have refs.
at invariant" . The problem was with stateless components, so that's why I instead of using arrow functions in Main.jsx and Nav.jsx, I used the React.createClass({}).
I don't know if I worded this right, so bear with me. Basically, I have a component that is a functioning counter (increments or decrements). The other component is a timer that counts down from (by default) 25 to 0.
Previously, I had the timer just set to the value of 25, but I am trying to have the timer change as the value of the counter changes, and when the use presses the "start" button, the timer will count down from whatever number was set by the counter.
I can get the components working individually, but not together.
I've tried setting this.state.currentCount to the value of this.props.time, and then changing the value of this.state.currentCount, but no luck. Either the timer doesn't budge or it doesn't reflect the value of the counter.
Not sure if I should be using componentWillReceiveProps instead.
Any help would be greatly appreciated. There's a screenshot at the bottom if that helps at all.
Session Component:
const Session = React.createClass({
getInitialState: function() {
return {
minutes: 25,
seconds: 0
};
},
increment: function() {
this.setState({ minutes: this.state.minutes + 1 });
},
decrement: function() {
this.setState({ minutes: this.state.minutes - 1 });
},
timeToString: function(time) {
return time + ':00';
},
render: function() {
return (
<section>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
{this.state.minutes}
<Clock time={this.state.minutes}/>
</section>
);
}
});
module.exports = Session;
Clock Component:
const Clock = React.createClass({
getInitialState: function() {
return { currentCount: this.props.time };
},
startTimer: function() {
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},
pauseTimer: function() {
clearInterval(this.state.intervalId);
this.setState({ intervalId: this.props.time });
},
timer: function() {
var newCount = this.state.currentCount - 1;
if (newCount >= 0) {
this.setState({ currentCount: newCount });
} else {
clearInterval(this.state.intervalId);
}
},
render: function() {
return (
<section>
<button onClick={this.startTimer}>Start</button>
<button onClick={this.pauseTimer}>Pause</button>
{this.props.time}
<br></br>
{this.state.currentCount}
</section>
);
}
});
module.exports = Clock;
getInitialState only runs when the component is first initialized so on next
updates from the parent component it won't run that function. You are correct
in that you want to use one of the lifecycle events and in this case
componentWillReceiveProps sounds like the most appropriate because you can
setState there and you don't need to wait for the component to render (otherwise
you would use componentDidUpdate).
I haven't checked this code but I think it should work with this addition:
const Clock = React.createClass({
...
componentWillReceiveProps: function(nextProps) {
// Perhaps pause timer here as well?
this.setState({
currentCount: nextProps.time
})
},
...
});
because your timer depends on Start button. it would be good if you set state of currentCount in startTimer method.
startTimer: function() {
if(this.state.intervalId)
clearInterval(this.state.intervalId); //clear the running interval
this.setState({ currentCount: this.props.time }); // reset currentcount
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},
I'm trying to understand how to send new variable if I use state.
It's example from React tutorial:
var Timer = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0};
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
});
React.render(<Timer />, mountNode);
It works. But If I want to send secondsElapsed what should I do?
<Timer sec={this.props.sec}>and:
getInitialState: function() {
return {secondsElapsed: this.props.sec};
}
It doesn't work.
JSFIDDLE - I should start timer form 10000 sec.
The code below works as expected. Other than making sure there's a default value always for the initialTime property, you can initialize the secondsElapsed state within getInitialState. While doing this could be considered an anti-pattern, in this case it's not as it's just doing initialization of internal state.
Also, you'll need to implement componentWillReceiveProps. This function is called (after the first time) when ever the props to the component have been updated. In this case, the initialTime property value was initially 10, and then changed to 10000. So, the nextProps argument will contain an object with the property initialTime set to 10000.
var Timer = React.createClass({
getInitialState: function() {
return { secondsElapsed: this.props.initialTime || 0 };
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentWillReceiveProps: function(nextProps) {
this.setState({ secondsElapsed: nextProps.initialTime || 0 });
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
});
If you might be passing a string into initialTime as shown above, make sure that you use parseInt on the value:
return { secondsElapsed: parseInt(this.props.initialTime || '0') };
Read more about the component lifecycle here.
Check out componentWillMount (http://facebook.github.io/react/docs/component-specs.html)
This if you call setState within componentWillMount you are guaranteed it will be updated before it hits render.