This article was first published on my blog.
I love CodeSandbox. It has pretty much replaced CodePen for me unless I am fiddling around with CSS or freeCodeCamp front-end projects.
I like going through the sandboxes and picking out different ones to look at, take apart, and figure out how they work.
While going through React Tutorial for Beginners by Kent C. Dodds on Egghead.io I decided I would look for sandboxes that correlate with the course as I was using Codesandbox to build out the stopwatch we were building in that course.
I found a sandbox which I forked and found it to be buggy.
Why didn't the stopwatch work? Glancing at the code for a few seconds, I saw some obvious problems right away.
Here is an example of the stopwatch being broken:
Bugfix 1
The first thing I noticed was on line 7:
class StopWatch extends React.Component {
state = { lapse: 0, running: false };
handleRunClick = () => {
const startTime = Date.now() - this.state.lapse;
setInterval(() => {
this.setState({
lapse: Date.now - startTime
});
});
this.setState({
running: true
});
};
Date.now()
needs parentheses. Date
is an an object constructor with .now()
being a method. When we click on the start button, React doesn't know what to do here; we aren't setting the state of lapse
to be a number, which we expect. By adding the parentheses, we get the start button to work. No more NaNms
.
But now we have another problem: the timer won't stop.
I also removed the console.log(Math.random());
because I felt it was unneeded.
Bugfix 2: Getting the Stopwatch to Stop and Clear
Each time the button is clicked, we set the state to either running
or lapse
. The timer runs when we click start
but clicking stop
or clear
doesn't seem to work. How can we fix this?
We can create a timer update function that accepts the current state. We can accomplish this by using native DOM APIs such as setInterval()
and clearInterval()
. We can run conditional logic to see if the timer is running:
//updater function
this.setState(state => {
if (state.running) {
clearInterval(this.timer);
} else {
const startTime = Date.now() - this.state.lapse;
this.timer = setInterval(() => {
this.setState({
lapse: Date.now() - startTime
});
});
}
and use Date.now()
to get the timestamp in ms, assign it a startTime
variable to compare the current time to the amount of time that has passed. When we click the start button, it sets the startTime
to the current timestamp. We also need to return a new state as state is not mutable..
class StopWatch extends React.Component {
state = { lapse: 0, running: false };
handleRunClick = () => {
//updater function
this.setState(state => {
if (state.running) {
clearInterval(this.timer);
} else {
const startTime = Date.now() - this.state.lapse;
this.timer = setInterval(() => {
this.setState({
lapse: Date.now() - startTime
});
});
}
// returning a new state to not mutate our original state
return { running: !state.running };
});
};
Okay so this partially works. But as you can see below, if I click clear
while the stopwatch timer is running, it doesn't clear the timer, and it also doesn't allow me to stop the timer, either.
How do we fix this particular bug?
If we look back at the previous code, we can see we are using clearInterval()
to reset the stopwatch timer. In our current iteration, our handleOnClear
method is just setting the state without clearing the previous state.
handleOnClear = () => {
this.setState({
lapse: 0,
running: false
});
};
We can fix this by adding clearInterval()
and passing in the timer function to the handleOnClear
method to clear the state.
handleOnClear = () => {
clearInterval(this.timer);
this.setState({
lapse: 0,
running: false
});
};
This will give us the results we want.
Potential Problem?
There is a memory leak in this particular iteration. The timer will run until it is explicitly stopped in the DOM. We can use a React lifecycle method to stop all processes in the DOM when this component is mounted or unmounted.
For this we can use componentWillUnmount
to tell React to unmount the component once it is done rendering.
componentWillUnmount() {
clearInterval(this.timer);
}
Thoughts and Conclusions
I find it much more enjoyable fixing other people's bugs than my own. This was a fun exercise and I plan on doing it more regularly and blogging about it.
This stopwatch is a stupid simple component but if you are just scratching the surface of React like me, I am sure digging into something like this stopwatch and figuring out how it works is an excellent exercise and use of one's time.
Top comments (6)
Excellent post! And this really encourages me, because I'm currently the opposite: delving into someone else's code intimidates me :)
It might seem a little intimidating, but what's the worst that could happen? You break something and have to do
git reset --hard head
? The computer will not explode... Unless you do something really, really awesome. Then I would love to know how you made your computer explode.You can do it!
Interesting post and great explanations. I'm torn on whether I enjoy troubleshooting someone else's code. At times it can be miserable depending on how much sauce was served with the spaghetti... But if it's finding fiddly little things like this it can be rewarding without being super tedious.
Agreed. It helps if you’re already familiar with the language, which I was. If it was something I was mildly familiar with I’d have probably felt differently for sure.
Great article. This is an interesting idea to find other's code and dig through it to see how it works, or doesn't work in some cases. :) I imagine this is what it would be like to work on a team of devs and have to fix bugs in code you didn't write. I'm looking forward to more posts on this subject.
Hey! This is awesome. Thank you for sharing your venture into getting into someone else's code :).
The more foreign the codebase, the more opportunities to learn. Keep on doing what you're doing.