FCC Pomodoro Clock failing in-built tests - javascript

I'm doing FCC exercise Pomodoro Clock that sort of works. I think it’s the problem that when countdown was in it’s last minute, pause won’t work. Btw pause works if it’s not last minute. My suspicion is that this problem causes other tests to fail. Also test say that reset functionality doesn’t work, but it works.
Edit:
Deeper explanation of my problem:
Method paused() is used to change state paused from false to true and vice-versa. That method is called inside a timer() method whose job also is to initiate setInterval. setInterval is been set in variable this.clearTimer. If it’s paused true then clearInterval() is initiated and timer is stopped.
When element with start_stop id is clicked, paused changes, evaluation occurs and as i said it before; false => setInterval goes; true => setInterval stops.
When unpaused, setInterval from values recorded in state. Problem is that when you pause timer which below one minute(59, 58 etc. sec.), it won’t resume countdown despite state of paused being changed from true to false?! If it’s countdown above 1min., start/pause works as prescribed.
You can see the code at: my pen
Here is the code of main component(from local source):
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './style.scss';
import Item from "./item.js";
import Arrow from "./arrow.js";
import Fa from "./fa.js";
class Pomodoro extends React.Component {
constructor(props) {
super(props);
this.state = {
breakLength: 5,
multiplier: 25,
base: 1000,
time: 0,
minutes: 25,
seconds: 60,
paused: false,
session: false,
break: true,
disabled: false,
};
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.timer = this.timer.bind(this);
this.reset = this.reset.bind(this);
this.paused = this.paused.bind(this);
this.myRef = React.createRef();
this.clearTimer = null;
}
componentDidMount(){
this.setState({
time: ""+(this.state.multiplier*this.state.base*60),
minutes: this.state.multiplier,
seconds: this.state.seconds === 60 ? "00" : this.state.seconds ,
});
}
paused(){
this.setState({
paused: !this.state.paused
});
}
timer(){
this.paused();
if(this.state.paused === false){
if((this.state.minutes!==0 && this.state.seconds!==0) || (this.state.seconds!==0)){
this.clearTimer = setInterval(() => {
if(this.state.session===false){
console.log("Sada ide session.");
this.setState({
time: ""+(this.state.time-this.state.base),
minutes: this.state.minutes > 0 ? Math.floor(((this.state.time-this.state.base)/(this.state.base))/60) : this.state.minutes,
seconds: this.state.seconds > 0 ? this.state.seconds-1 : 59,
session: (this.state.minutes===0 && this.state.seconds===1) ? true : false,
break: (this.state.minutes===0 && this.state.seconds===1) ? false : true,
});
}
if(this.state.break===false && this.state.session===true && this.state.time==="0"){
console.log("Kraj session-a. Sada ide resetovanje, pa break.");
this.setState({
time: ""+(this.state.breakLength*this.state.base*60),
minutes: this.state.breakLength,
seconds: 60,
});
}
if(this.state.break===false){
console.log("Sada ide break.");
this.setState({
time: ""+(this.state.time-this.state.base),
minutes: this.state.minutes > 0 ? Math.floor(((this.state.time-this.state.base)/(this.state.base))/60) : this.state.minutes,
seconds: this.state.seconds > 0 ? this.state.seconds-1 : 59,
session: (this.state.minutes===0 && this.state.seconds===1) ? false : true,
break: (this.state.minutes===0 && this.state.seconds===1) ? true : false,
});
}
if(this.state.break===true && this.state.session===false && this.state.time==="0"){
console.log("Kraj break-a. Sada ide resetovanje, pa session.");
this.setState({
time: ""+(this.state.multiplier*this.state.base*60),
minutes: this.state.multiplier,
seconds: this.state.seconds === 60 ? "00" : this.state.seconds,
});
}
}, this.state.base);
}
}
else{
clearInterval(this.clearTimer);
}
}
reset(){
this.myRef.current.pause();
this.myRef.current.currentTime = 0;
clearInterval(this.clearTimer);
this.clearTimer = null;
this.setState({
breakLength: 5,
multiplier: 25,
base: 1000,
time: ""+(25*1000*60),
minutes: 25,
seconds: 60,
paused: false,
session: false,
break: true,
disabled: false,
});
}
increment(e){
console.log(e.target.id);
let myId = e.target.id;
if(myId==="break-increment"){
this.setState({
breakLength: this.state.breakLength <60 ? this.state.breakLength+1 : this.state.breakLength,
});
}
else if(myId==="session-increment"){
this.setState({
multiplier: this.state.multiplier < 60 ? this.state.multiplier+1 : this.state.multiplier,
time: this.state.time !== "60" ? ""+((this.state.multiplier+1)*this.state.base*60) : this.state.time,
minutes: this.state.minutes < 60 ? this.state.multiplier+1 : this.state.minutes,
});
}
}
decrement(e){
console.log(e.target.id);
let myId = e.target.id;
if(myId==="break-decrement" && this.state.breakLength > 1){
this.setState({
breakLength: this.state.breakLength > 1 ? this.state.breakLength-1 : this.state.breakLength,
});
}
else if(myId==="session-decrement" && this.state.multiplier > 1 && this.state.time > 1 && this.state.minutes > 1){
this.setState({
multiplier: this.state.multiplier > 1 ? this.state.multiplier-1 : this.state.multiplier,
time: this.state.time > 1 ? (""+((this.state.multiplier-1)*this.state.base*60)) : this.state.time,
minutes: this.state.minutes > 1 ? this.state.multiplier-1: this.state.minutes,
});
}
}
render(){
//console.log(this.state);
const minutes = (""+this.state.minutes).length===1 ? "0"+this.state.minutes : this.state.minutes;
const seconds = this.state.seconds===60 ? "00" : ((""+this.state.seconds).length===1 ? "0"+this.state.seconds : this.state.seconds);
const time = minutes+":"+seconds;
if(time==="00:00"){
console.log("1: "+time);
console.log("2: "+this.state.minutes+":"+this.state.seconds);
this.myRef.current.play();
}
/*if((this.state.minutes+":"+this.state.seconds)===time){
alert("alert2: "+this.state.minutes+":"+this.state.seconds);
}*/
const lastSesMin = (minutes==="00") ? {color: 'red',} : {};
const decrement = this.clearTimer ? ()=>{} : this.decrement;
const increment = this.clearTimer ? ()=>{} : this.increment;
const item2Head = <h3 id="break-label">Break Length</h3>;
const fa1 = <Fa klasa={"fa fa-arrow-down fa-2x"} id={"break-decrement"} onClick={decrement}/>;
const fa2 = <Fa klasa={"fa fa-arrow-up fa-2x"} id={"break-increment"} onClick={increment}/>;
const arr1 = [<Arrow klasa={"arrow"} key={0} arrow={item2Head}/>, <br key={1}/>, <Arrow klasa={"arrow"} key={2} arrow={fa1}/>, <Arrow id={"break-length"} klasa={"nums"} key={3} arrow={this.state.breakLength}/> , <Arrow key={4} klasa={"arrow"} arrow={fa2}/>];
const item3Head = <h3 id="session-label">Session Length</h3>;
const fa3 = <Fa klasa={"fa fa-arrow-down fa-2x"} id={"session-decrement"} onClick={decrement}/>;
const fa4 = <Fa klasa={"fa fa-arrow-up fa-2x"} id={"session-increment"} onClick={increment}/>;
const arr2 = [<Arrow klasa={"arrow"} key={0} arrow={item3Head}/>, <br key={1}/>, <Arrow klasa={"arrow"} key={2} arrow={fa3}/>, <Arrow klasa={"nums"} id={"session-length"} key={3} arrow={this.state.multiplier}/> , <Arrow key={4} klasa={"arrow"} arrow={fa4}/>];
const countdownLabel = (this.state.session===false && this.state.break===true) ? "Session" : "Break";
const item4Head = <h3 key={0} id={"timer-label"} style={lastSesMin}>{countdownLabel}</h3>;
const nums2 = <div key={1} className="nums" style={lastSesMin} id={"time-left"}>{time}</div>;
const arr3 = [item4Head, nums2];
const fa5 = <Fa key={0} klasa={"fa fa-play arrow controls"} title={"start-pause"}/>;
const fa6 = <Fa key={1} klasa={"fa fa-pause arrow controls"} title={"start-pause"}/>;
const fa7 = <Fa key={2} klasa={"fa fa-refresh arrow controls"} id="reset" title={"reset"} onClick={this.reset}/>;
const startPause = <div id="start_stop" key={4} onClick={this.timer}>{fa5}{fa6}</div>;
const arr4 = [startPause, fa7];
return(
<div className="grid-container cent">
<Item klasa={"item1"} arrowsAndNums={"Pomodoro Clock"}/>
<Item klasa={"item2"} arrowsAndNums={arr1}/>
<Item klasa={"item3"} arrowsAndNums={arr2}/>
<Item klasa={"item4"} arrowsAndNums={arr3}/>
<Item klasa={"item4"} arrowsAndNums={arr4}/>
<audio ref={this.myRef} id="beep" src="http://soundbible.com/grab.php?id=2158&type=wav"></audio>
</div>
);
}
}
ReactDOM.render(<Pomodoro/>, document.getElementById('root'));
Edit2:
I resolved pausing issue. I just changed line if(this.state.minutes!==0 && this.state.seconds!==0){ to line if((this.state.minutes!==0 && this.state.seconds!==0) || (this.state.seconds!==0)){.
Here are some images that show test errors even if there should be no errors.
PS: For error testing of the exercise it has been used fcc's error testing script, that is what generates these supposed errors.
Image1 of the errors:
Image2 of the errors:
Edit3:
And image of an error which might come out of testing suite:
Edit4:
As i recently discovered, even FCC's pomodoro fails tests sometimes. Sometimes in different browsers, sometimes on sundays, sometimes etc. ....
Long story short, i devised new code, that addresses issues in previous edits i made. Still, it suffers same problems as forementioned fcc's pomodoro. I read somewhere, something about timing events, whose implementation and execution depends on browsers and in this case test suite itself uses to, as also is my pomodoro. So there should be "conflicts" of sorts ... Resolving these inconsistencies and conflicts, so that in every browser, in every suit/test, looks insurmountable and i guess would ... What i'm trying to ask, is it ok if i submit my pomodoro, concerning everything being said?
My improved pomodoro
Added comments that explain functioning in the code so feel free to browse.
Image of my test fail:
On the upper left you can see 28/29 passed tests.
Notice: you can see updated code in codepen together with comments.

Just read some code (without testing your sandbox), but I found a problem that can lead to some obscure bugs due to possible race conditions:
paused(){
this.setState({
paused: !this.state.paused
});
}
timer(){
this.paused();
if(this.state.paused === false){
// ...
timer() is calling paused, where the state is updated. Then timer() checks the new state.
Problems:
setState may be batched, so consecutive calls to paused may be based on the some value.
Solution: use a function as state updater:
setState(prevState => ({
paused: !prevState.paused
}));
setState may be asynchronous (same link), so paused state may not reflect that change when being read in timer()!
Solution: use a callback as second parameter to setState():
setState(prevState => ({
paused: !prevState.paused
}), () => {
// You can read updated this.state.paused here....
});
So the whole code fragment could be implemented like this:
paused(callback){
setState(prevState => ({
paused: !prevState.paused
}), callback);
}
timer(){
this.paused(() => {
// if(this.state.paused === false){
// ...
});
But if you have many such places, you can quickly get in some kind of callback hell. And managing stopping/starting a timer in parallel to these state changes can become quite tricky by its own. You could at least simplify your timer but letting it always run (start in componentDidMount(), stop in componentWillUnmount()) and deciding on each tick what to do (e.g. doing nothing when being "paused", or letting a "pause" indicator blink ...)
Side note: variables like base do not need to be in state because they are just internal variables which don't need to trigger re-rendering of the component.

Related

How to optimize images to imporve website performance in React.js

I am currently working on a website with the use of React.js. The website includes an automatic imageslider/carousel which is really effecting the performance of the website and slows it down significantly.
I have the following code for the image slider:
import React, { useState, useEffect } from "react";
import images from "../../constants/images";
import "../CSS/carousel.css";
import { BsArrowLeft, BsArrowRight } from "react-icons/bs";
const carouselImages = [
images.carousel_one,
images.carousel_two,
images.carousel_three,
images.carousel_four,
images.carousel_five,
images.carousel_six,
images.carousel_seven,
images.carousel_eight,
images.carousel_nine,
images.carousel_ten,
images.carousel_eleven,
images.carousel_twelve,
images.carousel_thirteen,
images.carousel_fourteen,
];
const Carousel = () => {
const [current, setCurrent] = useState(0);
useEffect(() => {
const slideInterval = setInterval(() => {
setCurrent((current) =>
current < carouselImages.length - 1 ? current + 1 : 0
);
}, 3000);
return () => clearInterval(slideInterval);
}, []);
const length = carouselImages.length;
const nextSlide = () => {
setCurrent(current === length - 1 ? 0 : current + 1);
};
const prevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1);
};
if (!Array.isArray(carouselImages) || carouselImages.length <= 0) {
return null;
}
const switchIndex = (index) => {
setCurrent(index);
};
return (
<div className="app__carousel">
<div className="app__carousel-inner_container">
{carouselImages.map((slide, index) => (
<div
className={
index === current
? "app__carousel-slide_active"
: "app__carousel-slide"
}
key={index}
>
{index === current && (
<img src={slide} className="app__carousel-image" />
)}
</div>
))}
<BsArrowLeft onClick={prevSlide} className="app__carousel-left_arrow" />
<BsArrowRight
onClick={nextSlide}
className="app__carousel-right_arrow"
/>
</div>
<div className="app__carousel-indicator_container">
{carouselImages.map((_, index) => (
<button
className={`app__carousel-indicator_btn${
current === index ? " active" : ""
}`}
onClick={() => switchIndex(index)}
></button>
))}
</div>
</div>
);
};
export default Carousel;
I have already used the 'imagemin' plugin along with a craco.config.js file to try to optimize the images. My craco file looks like this:
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
module.exports = {
webpack: {
configure: (webpackConfig) => {
webpackConfig.optimization.minimize = true;
webpackConfig.optimization.minimizer.push(
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminMinify,
options: {
plugins: [["mozjpeg", { quality: 85 }]],
},
},
generator: [
{
preset: "webp",
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
"imagemin-webp",
"imagemin-pngquant",
"imagemin-svgo",
],
},
},
],
})
);
return webpackConfig;
},
},
};
Another thing that I have done is encoded the images with WebP by running
npm i imagemin-webp -D
and importing my images as shown below:
import carousel_one from "../assets/carousel_one.jpg?as=webp";
The steps which I have already taken have only improved the performance by a few points in the lighthouse testing. Is there anything else that can be added or imporved in the already existing code to improve the performance more?
You can further optimize images by using AVIF instead of WEBP, which in most cases should offer even better compression than WEBP with the same image quality. If you want to push this step further, there are AI compressors available online, that are able to shrink the size without loosing quality even further (This you will either have to do manually each time you want to shrink image size, or in most instances pay for API).
Another option is to serve images that are the minimum required size for the resolution you are using. This means either using <picture> or srcset attribute, or making your own solution in JavaScript.
But the most common solution to pictures slowing down page remain lazy loading.

Read/Show more or less of html content or normal text using single reusable React JS component

When I was working with HTML content displayed with Read more and Read less(Show more or Show less) on my webpage, I struggled a lot googling to get a quick solution. Unfortunately, I failed to collect a single source of the solution. I started writing my own solution and decided to share my own solution if it helps someone.
At first, You need to install "html-truncate"
npm i html-truncate
import React, {useState, useCallback, useEffect} from 'react';
import truncate from "truncate-html";
const ReadMoreMaster = (props) => {
let {byWords,parentClass, readLess, readMore, length, ellipsis, spaceBefore} = props;
const [showMore, setShowMore] = useState(false);
truncate.setup({
byWords: !!byWords,
ellipsis: ellipsis ? ellipsis : '',
})
const [state, setState] = useState({
showRealData: false,
realData: props.children,
truncatedData: truncate(props.children, length ? length : 2)
});
const handleShowData = useCallback(() => {
setState(prevState => ({
...prevState,
showRealData: !prevState.showRealData
}));
}, [setState]);
useEffect(() => {
if(byWords && props.children) {
const textDetails = props.children.replace(/<[^>]+>/g, '');
const arr = textDetails.split(" ");
if(arr.length > length) {
setShowMore(true);
}
}
}, [byWords])
return (
<div className={parentClass}>
<div
dangerouslySetInnerHTML={{
__html: `${
!state.showRealData ? state.truncatedData : state.realData
}`
}}
/>
{spaceBefore === false ? '' : ' '}
{
showMore &&
<a href={void (0)} className="read-more" onClick={handleShowData}>
{!state.showRealData ? readMore ? readMore : 'Read More' : readLess ? readLess : 'Read Less'}
</a>
}
</div>
);
};
export default ReadMoreMaster;
Now call the component like below:
<ReadMoreMaster
byWords={true}
length={150}
ellipsis="..."
>
<p>your content with or without HTML tag</p>
</ReadMoreMaster>
Supported props are:
// ...Supported Props...
// parentClass="test" || default value : null
// byWords={true} || default value : false
// ellipsis="..." || default value : null
// length={5} || default value : 2
// readMore="See More" || default value : Read More
// readLess="See less" || default value : Read Less
// spaceBefore={false} || default value : true
All done. Enjoy!!!

Does React wait for render to update state?

I am checking the state of one of my keys twice within a function. However, even after operations are done on the key value it still reports the same value.
So I have a function called clickMe that will run from an onClick event for a button I have in the return for the component. When it's clicked the function should check to see the current state of totalRemaining before performing operations. If totalRemaining is greater than 0 it should uptick clicks and downtick totalRemaining. After that operation it should check to see if totalRemaining is still greater than 0. If it isn't it should disable the button. However, before and after the operation the value of totalRemaining is the same even though after render it is clear that it has been updated.
import React from 'react';
class Item extends React.Component{
constructor(props){
super(props);
this.state = {
clicks: 0,
totalRemaining: 10,
disabled: false,
}
}
clickMe(){
if(this.state.totalRemaining>0){
console.log(`Clicks: ${this.state.clicks}`)
console.log(`Total Remaining: ${this.state.totalRemaining}`)
this.setState({
clicks: this.state.clicks + 1,
totalRemaining: this.state.totalRemaining - 1
});
console.log(`Clicks: ${this.state.clicks}`)
console.log(`Total Remaining: ${this.state.totalRemaining}`)
if(this.state.totalRemaining===1){
this.setState({
disabled: true
})
}
}else{
this.setState({
disabled: true
})
}
}
render() {
return(
<div>
<button onClick={() => this.clickMe()} disabled={this.state.disabled}>Click Me {this.props.name}</button>
<input placeholder={this.state.totalRemaining}/>
<input placeholder={this.state.clicks}/>
</div>
)
}
}
export default Item;
Here is the console output:
this.state.totalRemaining
10
MyItem.js:18 Clicks: 0
MyItem.js:19 Total Remaining: 10
this.state.totalRemaining
10
this.state.totalRemaining
10
MyItem.js:25 Clicks: 0
MyItem.js:26 Total Remaining: 10
this.setState({
clicks: this.state.clicks + 1,
totalRemaining: this.state.totalRemaining - 1
},() => {this.state.totalRemaining < 1 ? this.setState({
disabled: true
}) : null});
reference: https://stackoverflow.com/a/54713679/11229002

Creating independent stopwatches for each item in array. Setting the stopped time, based on data returned by the API

Creating independent stopwatches. I have two elements named A andB. When I click on the A element, its descriptionHello and stopwatch will appear. When I click on the B element, itsWorld description and stopwatch will appear. I have a problem with stopwatches. When I click on the element A and start the stopwatch, go to the elementB then this stopwatch is running. My goal is that when I run the stopwatch for the element A it will count only for this element. When he stops the stopwatch in the element A, and go to the elementB, then in this element the stopwatch will count only for this element. I stop the stopwatch in the B element and go to theA element and I will be able to resume the stopwatch. I am asking for some ideas to solve this problem.
I send by calling the startTime function (method post -> object with the starting date). I click stop -> calls stopTimer (method post -> I send the object with the end date). In response, the item is debossed with the starting date and end date and the number of seconds (the difference between the end date and the starting date) is saved in the state. On the basis of these data (start date, end date and second), set the time at which the stopwatch was stopped. How do I close my browser to download this data to set the time at which it was stopped.
Please, give me some tips. I will correct my code on a regular basis and insert it here.
Expected effect:
Click element A -> start stopwatch -> stopwatch stop -> click elementB -> start stopwatch -> return to element A -> resume the timer on the time it was stopped
The whole code here: https://stackblitz.com/edit/react-x9h42z
Part of the code:
App.js
class App extends React.Component {
constructor() {
super();
this.state = {
items: [
{
name: 'A',
description: 'Hello'
},
{
name: 'B',
description: 'World'
}
],
selectIndex: null
};
}
select = (index) => {
this.setState({
selectIndex: index
})
}
render() {
console.log(this.state.selectIndex)
return (
<div>
<ul>
{
this.state.items
.map((item, index) =>
<Item
key={index}
index={index}
item={item}
select={this.select}
items = {this.state.items}
selectIndex = {this.state.selectIndex}
/>
)
}
</ul>
<ItemDetails
items = {this.state.items}
selectIndex = {this.state.selectIndex}
/>
</div>
);
}
}
Stopwatch
class Stopwatch extends Component {
constructor() {
super();
this.state = {
timerOn: false,
timerStart: 0,
timerTime: 0
};
}
startTimer = () => {
this.setState({
timerOn: true,
timerTime: this.state.timerTime,
timerStart: Date.now() - this.state.timerTime
});
this.timer = setInterval(() => {
this.setState({
timerTime: Date.now() - this.state.timerStart
});
}, 10);
};
stopTimer = () => {
this.setState({ timerOn: false });
clearInterval(this.timer);
};
resetTimer = () => {
this.setState({
timerStart: 0,
timerTime: 0
});
};
render() {
const { timerTime } = this.state;
let centiseconds = ("0" + (Math.floor(timerTime / 10) % 100)).slice(-2);
let seconds = ("0" + (Math.floor(timerTime / 1000) % 60)).slice(-2);
let minutes = ("0" + (Math.floor(timerTime / 60000) % 60)).slice(-2);
let hours = ("0" + Math.floor(timerTime / 3600000)).slice(-2);
return (
<div>
<div className="Stopwatch-display">
{hours} : {minutes} : {seconds} : {centiseconds}
</div>
{this.state.timerOn === false && this.state.timerTime === 0 && (
<button onClick={this.startTimer}>Start</button>
)}
{this.state.timerOn === true && (
<button onClick={this.stopTimer}>Stop</button>
)}
{this.state.timerOn === false && this.state.timerTime > 0 && (
<button onClick={this.startTimer}>Resume</button>
)}
{this.state.timerOn === false && this.state.timerTime > 0 && (
<button onClick={this.resetTimer}>Reset</button>
)}
</div>
);
}
}
What you need is to create two instances of stopwatches one for each list item. I have made changes to the link link you provided.
I added stopwatch in your list array to each object with a unique key for React to know that they are a different component.
Now, I am simply rendering all the list items with stopwatches and to maintain the state of each stopwatch even after the switch I am just using a simple display none technique rather than removing the component altogether.
Check the code and let me know if it works for you?
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
class Item extends Component {
render() {
const selectItem = this.props.items[this.props.selectIndex]
console.log(selectItem);
return (
<li onClick={() => this.props.select(this.props.index)}>
<div>
Name:{this.props.item.name}
</div>
</li>
)
}
}
class ItemDetails extends Component {
render() {
const selectItem = this.props.items[this.props.selectIndex]
console.log(selectItem);
let content = this.props.items.map((item, index) => {
return (
<div className={this.props.selectIndex === index?'show':'hide'}>
<p>
Description:{item.description}
</p>
{item.stopWatch}
</div>
);
})
return (
<div>
{selectItem ?
content
:
null
}
</div>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
items: [
{
name: 'A',
description: 'Hello',
stopWatch: <Stopwatch key={1} />
},
{
name: 'B',
description: 'World',
stopWatch: <Stopwatch key={2} />
}
],
selectIndex: null
};
}
select = (index) => {
this.setState({
selectIndex: index
})
}
render() {
console.log(this.state.selectIndex)
return (
<div>
<ul>
{
this.state.items
.map((item, index) =>
<Item
key={index}
index={index}
item={item}
select={this.select}
items = {this.state.items}
selectIndex = {this.state.selectIndex}
/>
)
}
</ul>
<ItemDetails
items = {this.state.items}
selectIndex = {this.state.selectIndex}
/>
</div>
);
}
}
class Stopwatch extends Component {
constructor() {
super();
this.state = {
timerOn: false,
timerStart: 0,
timerTime: 0
};
}
startTimer = () => {
this.setState({
timerOn: true,
timerTime: this.state.timerTime,
timerStart: Date.now() - this.state.timerTime
});
this.timer = setInterval(() => {
this.setState({
timerTime: Date.now() - this.state.timerStart
});
}, 10);
};
stopTimer = () => {
this.setState({ timerOn: false });
clearInterval(this.timer);
};
resetTimer = () => {
this.setState({
timerStart: 0,
timerTime: 0
});
};
render() {
const { timerTime } = this.state;
let centiseconds = ("0" + (Math.floor(timerTime / 10) % 100)).slice(-2);
let seconds = ("0" + (Math.floor(timerTime / 1000) % 60)).slice(-2);
let minutes = ("0" + (Math.floor(timerTime / 60000) % 60)).slice(-2);
let hours = ("0" + Math.floor(timerTime / 3600000)).slice(-2);
return (
<div>
<div className="Stopwatch-display">
{hours} : {minutes} : {seconds} : {centiseconds}
</div>
{this.state.timerOn === false && this.state.timerTime === 0 && (
<button onClick={this.startTimer}>Start</button>
)}
{this.state.timerOn === true && (
<button onClick={this.stopTimer}>Stop</button>
)}
{this.state.timerOn === false && this.state.timerTime > 0 && (
<button onClick={this.startTimer}>Resume</button>
)}
{this.state.timerOn === false && this.state.timerTime > 0 && (
<button onClick={this.resetTimer}>Reset</button>
)}
</div>
);
}
}
render(<App />, document.getElementById('root'));
h1, p {
font-family: Lato;
}
.show {
display: block;
}
.hide {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Your stopwatch does not update as your render method always returns <Stopwatch />,
so even if selectItem changes react does not render a new <Stopwatch /> component
for you, it shows the old one.
return (
<div>
{selectItem ?
<div>
<p>Description:{selectItem.description}</p>
<Stopwatch />
</div>
:
null
}
</div>
)
For react to render a new component for you you need to pass a key property to your
component.
return (
<div>
{selectItem ?
<div>
<p>Description:{selectItem.description}</p>
<Stopwatch key={selectItem.name}/>
</div>
:
null
}
</div>
)
Now react renders new component for you when you switch between stopwatches, but
every time you do that stopwatch resets, as the component itself it re-rendered
initializing your state variables.
This is where state management pops in. You can use REDUX to manage your component state.
You can also write a simple service to do it for you if you want your stopwatch to run
in the background.
Demo: stackblitz.
What you want is actually have several stop watches (one for each item) but show only one of them (which is the selected one)
On the ItemDetails component, Replacing:
<Stopwatch />
with
{this.props.items.map((_, index) => (
<div style={{ display: index === this.props.selectIndex ? "block" : "none" }}>
<Stopwatch />
</div>
))}
Will solve your problem as showed here: https://stackblitz.com/edit/react-atci76
That way react is taking care of two stop watches (cycle and states and all) but your HTML is only showing one of them.
Alternatively, if you are going to show lots of stopwatches, you can have one Stopwatch state passed as property to your Stopwatch object and then switch the state using the button
The problem:
You composition is wrong. Even though you are rendering one <Item /> for each element of the state your <ItemDetails /> is only called once, so the state will be always the same, doesn't matter which item is current selected. This is happening because your component <ItemDetails /> knows all details and only changes between then, not changing the Stopwatch /> component.
Possible Solution
There is actually a few alternatives to this problem, my favorite is:
Make the Item component standalone:
You could, instead of having one component to display details of the current selected user, have single component which knows how to display it's details an knows the state of it's own <Stopwatch /> and only show details and stopwatch if is selected. Something like this:
class Item extends Component {
render() {
const selectItem = this.props.items[this.props.selectIndex]
console.log(selectItem);
return (
<li onClick={() => this.props.select(this.props.index)}>
<div>
Name:{this.props.item.name}
</div>
{this.props.isSelected ?
<div>
<p>
Description:{selectItem.description}
</p>
<Stopwatch />
</div>
:
null
}
</li>
)
}
}
This way you have a decoupled way of dealing with multiple items with different times.
This is actually not exactly what you are looking for, cause even like this you still have your Stopwatches components beeing mounted and unmounted so the state gets reseted everytime, to workaround this you need a way to "remember" the previous state of each counter or never unmount then, just hide those who aren't selected.
I've altered your fiddle to reflect 2 <Stopwatch /> running separately
You could change the Stopwatch component to store timers by a label name.
The Stopwatch will have a label prop.
When the label changes you could store the current timer and load a new one.
The new one can be an empty one or one that is already known, loaded from state.
class Stopwatch extends Component {
// moved the empty timer for easy reusing
defaultTimer = {
timerOn: false,
timerStart: 0,
timerTime: 0,
time: {
startTime: "",
endTime: "",
seconds: ""
}
};
state = {
// We store the saved timers in here.
// For convenience of finding the I used an object to save them
knownTimers: {},
...this.defaultTimer
};
componentDidUpdate(prevProps) {
// when the label changes
if (prevProps.label !== this.props.label) {
const { knownTimers, timerOn, timerStart, timerTime, time } = this.state;
// we stop any running timers
clearInterval(this.timer);
// we build the _timer_ we want save
const knownTimerToSave = { timerOn, timerStart, timerTime, time };
// if the label identifies a known _timer_ we load that one
// if not we load a new timer
const loadedTimer = knownTimers[this.props.label]
? knownTimers[this.props.label]
: this.defaultTimer;
this.setState({
// we load the current timer
...loadedTimer,
// we overwrite `knownTimers` with the save timers
knownTimers: {
...knownTimers,
[prevProps.label]: {
...knownTimerToSave,
// we make sure we don't save a running timer
timerOn: false
}
}
});
}
}
startTimer = () => {
this.setState({
timerOn: true,
timerTime: this.state.timerTime,
timerStart: Date.now() - this.state.timerTime
});
this.timer = setInterval(() => {
this.setState({
timerTime: Date.now() - this.state.timerStart
});
}, 10);
};
stopTimer = () => {
this.setState({ timerOn: false });
clearInterval(this.timer);
};
resetTimer = () => {
this.setState({
timerStart: 0,
timerTime: 0
});
};
render() {
const { timerTime } = this.state;
let centiseconds = ("0" + (Math.floor(timerTime / 10) % 100)).slice(-2);
let seconds = ("0" + (Math.floor(timerTime / 1000) % 60)).slice(-2);
let minutes = ("0" + (Math.floor(timerTime / 60000) % 60)).slice(-2);
let hours = ("0" + Math.floor(timerTime / 3600000)).slice(-2);
return (
<div>
<div className="Stopwatch-display">
{hours} : {minutes} : {seconds} : {centiseconds}
</div>
{this.state.timerOn === false && this.state.timerTime === 0 && (
<button onClick={this.startTimer}>Start</button>
)}
{this.state.timerOn === true && (
<button onClick={this.stopTimer}>Stop</button>
)}
{this.state.timerOn === false && this.state.timerTime > 0 && (
<button onClick={this.startTimer}>Resume</button>
)}
{this.state.timerOn === false && this.state.timerTime > 0 && (
<button onClick={this.resetTimer}>Reset</button>
)}
</div>
);
}
}
If the label changed when a timer is running this is automatically turned off.
In order for this to work Stopwatch must be rendered once - not displayed conditionally. When the component unmounts the state clears and you loose all the known timers.
Here is demo of the component in action
There are other ways to accomplish this. Hope this helps!

Change color of selected page number (pagination)

I have created a simple React component with a table displaying 10 rows of data per page. The implementation of the pagination works as intended, but I want to be able to highlight the selected page.
In my constructor I have an initial state "active: null", which I then modify in a handleClick function.
constructor(props) {
super(props);
this.state = {
currentPage: 1,
eventsPerPage: 10,
active: null,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(index) {
this.setState({ currentPage: Number(index.target.id) });
if (this.state.active === index) {
this.setState({ active: null });
} else {
this.setState({ active: index });
}
}
And a function for setting the font-weight to bold.
myStyle(index) {
if (this.state.active === index) {
return 'bold';
}
return '';
}
Here is what I render.
const renderPageNumbers = pageNumbers.map((number, index) => (
<p
style={{ fontWeight: this.myStyle(index) }}
key={number}
id={number}
onClick={index => this.handleClick(index)}
>
{number}
</p>
));
return (
<div id="page-numbers">
{renderPageNumbers}
</div>
);
FYI: It's not the complete code.
I am not quite sure where I go wrong, any help is appreciated :)
pageNumbers.map((number, index) => index is zero based and am sure number starts from 1 so when index === 0 and number === 1, you would have this.myStyle(0) but
this.handleClick(0) will not work because you first #id === 1 not 0
why not just use number for your functions and remove index to safe confusion
You need to bind the context of the class to myStyle function in order to use this inside it.
So, in your constructor you should add it like this:
(see the last line)
constructor(props) {
super(props);
this.state = {
currentPage: 1,
eventsPerPage: 10,
active: null,
};
this.handleClick = this.handleClick.bind(this);
this.myStyle = this.myStyle.bind(this);
}

Categories

Resources