How to reset the timer component upon completion - javascript

I am trying to build a timer component using the react-countdown-now package: https://www.npmjs.com/package/react-countdown-now#key.
I was having trouble to reset the timer so that it moves on to the next time on the schedule.
I have been trying to use key property in props it pass it in an array of times to wait till (it was in the documentation). In reality I would get these values of a schedule from a server side method.
Currently I have
Component:
<Countdown
date={Date.now() + 5000}
key = {timeDelays}
intervalDelay={0}
precision={3}
renderer={timerRenderer}
/>
Supporting functions and Values:
//These time values are most probably going to be in JSON format,
//and probably will contain EPOCH times for scheduled events
const timeDelays = [2000,4000,3000,15789,2345794];
// Random component
const Completionist = () => <span>You are good to go!</span>;
// Renderer callback with condition
const timerRenderer = ({ hours, minutes, seconds, completed }) => {
// if (completed) {
// Render a completed state
// return <Completionist />;
// } else {
// // Render a countdown
return <span>{hours}:{minutes}:{seconds}</span>;
//}
};
I want it to start with a countdown from the list and then when completed move onto the next schedule value from the list.

This is a total change from the former answer, which used a class-based component.
First, we'll need to import react and react hooks to our component file.
import React, { useState } from 'react';
Next, we'll declare a react function component and use react hooks to maintain state.
function MyCountdownTimer({ times }) {
// a hook for the current time index
const [currentTimeIndex, setCurrentTimeIndex] = useState(0);
// a hook for the current time
const [currentTime, setCurrentTime] = useState(null);
// return a render
return (
<Countdown
date={currentTime}
key={currentTimeIndex}
onComplete={() => {
// dont's move to next time if just done with last time
if(times.length - 1 <= times.indexOf(currentTime)) return;
// move to next time index
setCurrentTimeIndex(currentTimeIndex + 1);
// reset current time
setCurrentTime(new Date(times[currentTimeIndex + 1]));
}}
renderer={({ hours, minutes, seconds, completed }) => {
// render completed
if (completed) return <span>You are good to go!</span>;
// render current countdown time
return <span>{hours}:{minutes}:{seconds}</span>;
}}
/>
);
}
An implementation of this would look something like so.
let times = [...] // an array of times
<MyCountdownTimer times={times} />
React hooks are still a bit new so for a better understanding on React Hooks you can follow this link https://reactjs.org/docs/hooks-intro.html.
NOTE
You need a way to tell what time you're currently on so within your component you'll have two things. The list of times(times) as an array this should be passed as a prop as suggested in the code above, the index of the current time(currentTimeIndex) as an integer and the current time(currentTime) as a Date object.
You'll need to listen for when the timer hits zero using the onComplete property to define a callback method, we do not update the component's state when the Countdown timer has been completed.
A key property on the Countdown component, this is meant to change for each time you want to reset the countdown timer and since we're incrementing the index to go to the next time we'll simply use the index of the current time.
I reduced the code of your renderer so you can render what you need to within the same function, except if you will be adding a lot more code in there than that.
This is using a function component with hooks to maintain state.
The date according to the documentation can be a date object.
Hope this helps answer your question.

Just you need to change the value of key attribute key={new values or updated value} so automatically re set the timer.
key
This is one of React's internal component props and is used to identify the component. However, we can leverage this behaviour and use it to, for example, restart the countdown by passing in a new string or number.https://www.npmjs.com/package/react-countdown

I guess my translated component would look like this
const WebPage = (props) => {
const timerState = {
times:[Date.now()+5000,Date.now()+12000,Date.now()+17000,Date.now()+22000],
currentTimeIndex: 0,
currentTime: Date.now(),
} ;
const timerRenderer = ({ hours, minutes, seconds, completed }) => {
if (completed) return <span> No more Scheduled time</span>;
return <span>{hours}:{minutes}:{seconds}</span>;
};
const completeTime = () => {
if (timerState.times.length - 1 <= times.indexOf(timerState.currentTime)) return;
// move to next time
timerState.currentTimeIndex++;
timerState.currentTime = new Date(timerState.times[timerState.currentTimeIndex+1])
};
return (
<Countdown
date={timerState.currentTime}
key = {timerState.currentTimeIndex}
onComplete={completeTime}
intervalDelay={0}
precision={3}
renderer={timerRenderer}
/>
)
}
It's not exactly working as it by default goes to "No more Scheduled time", and if I get rid of the if(completed) it just stays at 0:0:0.

Related

Detecting a prop value change

I am very new to react. I am currently creating a game and trying to detect if the current turn has changed. I am wondering if there is a simple way to do this.
let hasTurnChanged = props.turn % 2 == 1;
function chooseBestPrice() {
// start by seeing if prices fluctuate every turn
let currBestPrice = props.price;
console.log(hasTurnChanged);
if(hasTurnChanged){
currBestPrice = fluctuatePrice(props.price);
}
return currBestPrice;
}
When I click a button called Turn the prices are suppose to change.
Assuming you're trying to detect a prop come from parent component, useEffect could help with this.
All we need to do is put the prop into the dependencies array of useEffect.
const ChildComponent = (props) => {
useEffect(() => {
// call the function here
}, [props.price])
// ...other code
}
See the official document for more information.

a function that supposed to return a different value each time does not work in react

I am working on my portfolio, but when I reach the experiences page I tried to create a title that has it's middle part change every second, and value of it must come from an already set up array,
but when I run the code it always return the first string of the array,
can anyone please fix this problem for me ?
const projectsTitleKeyWords = ['responsible', 'meaningful', 'beautiful']
let titlep2 = 'test'
let index = 0
const change = () => {
titlep2 = projectsTitleKeyWords[index]
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
change()
console.log(titlep2)
const titlep1 = 'I creat '
const titlep1Array = titlep1.split('')
let titlep2Array = titlep2.split('')
const titlep3 = ' projects'
const titlep3Array = titlep3.split('')
the value of titlep2Array will be received by
<AnimatedLetters
letterClass={letterClass}
strArray={titlep2Array}
idx={15}
id='to-change'
/>
In-order to reflect UI changes in React, a component must re-render.
A React component re-renders in 1 of 2 scenarios:
Whenever there's a change in the value of a local state.
Whenever any of it's parent components re-render.
Therefor, since changes in the UI are only reflected upon a re-render, we should manage a local state that would be responsible for this behavior.
With functional components in React, this can be achieved via the useState hook.
In your case, we can simply make titlep2 a state, instead of a regular variable.
Example:
const [titlep2, setTitlep2] = useState('')
const change = () => {
setTitlep2(projectsTitleKeyWords[index])
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
<AnimatedLetters
letterClass={letterClass}
strArray={titlep2.split('')}
idx={15}
id='to-change'
/>
Note: since this function now updates the state, we can't call it the way you did in your example, since it will run every time the component re-renders, making the component re-render indefinitely due to the change in state.
Therefor, we can use the useEffect hook in-order to allow it to run only once on the initial render.
Example:
const change = () => {
setTitlep2(projectsTitleKeyWords[index])
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
useEffect(() => {
change()
}, [])
Furthermore, if there are any other variables that should reflect changes in the UI, they can be convert to states as well.
for that try using the setInterval() instead of setTimeout().
You are trying to make the text change after a specific interval in this case 1 second.
You should also consider doing that will CSS animations, it seems this is overkill.
const change = () => {
titlep2 = projectsTitleKeyWords[index]
index = ++index % projectsTitleKeyWords.length
console.log(titlep2)
setInterval(change, 1000)
}
change()

How do I update useState immidiatly for function React

I have created a function to calculate the winner between the two selected pokemon. However, instead of using the newly selected option, it is using the previously selected option. It has been brought to my attention that this is because useState is not updating immediately so how would I go about fixing this?
Here is my winner function:
function selectedWinner(){
console.log(pokemonName+' '+pokeOneTotal);
console.log(pokemonName2+' '+pokeTwoTotal);
if(pokeOneTotal>pokeTwoTotal){
setPokemonWinner(pokemonName);
}else if(pokeOneTotal<pokeTwoTotal){
setPokemonWinner(pokemonName2);
}else{
setPokemonWinner("Draw");
}
}
I have set it so that it is called in the different select functions, which are on click functions, here is one as an example:
function optionOneSelected(){
console.log('selected');
axios.get('https://pokeapi.co/api/v2/pokemon/'+ pokemonOne.current.value)
.then((res)=>{
let data=res.data;
console.log(data);
let type = data.types[0].type.name;
let id = data.id;
let height= data.height;
let weight = data.weight;
let name = data.forms[0].name;
let hp = data.stats[0].base_stat;
//console.log(type)
setPokemonType(type);
setPokemonId(id);
setPokemonHeight(height);
setPokemonWeight(weight);
setPokemonName(name);
setPokemonHp(hp);
let sum=0;
sum= data.stats[0].base_stat+ data.stats[1].base_stat+ data.stats[2].base_stat+ data.stats[3].base_stat+data.stats[4].base_stat+data.stats[5].base_stat;
setPokeOneTotal(sum);
let pokemonOneDataList = [
data.stats[0].base_stat, data.stats[1].base_stat, data.stats[2].base_stat, data.stats[3].base_stat,data.stats[4].base_stat,data.stats[5].base_stat
];
let labels = [
'hp', 'Attack', 'Defense', 'Special Attack', 'Special Defense', 'Speed'
];
setPokemonOneData(pokemonOneDataList);
setDataLabels(labels);
selectedWinner();
})
}
You can call useEffect with pokeOneTotal and pokeTwoTotal as dependencies. Whenever pokeOneTotal or pokeTwoTotal updates, it will trigger useEffect
useEffect(() => {
if(pokeOneTotal>pokeTwoTotal){
setPokemonWinner(pokemonName);
}else if(pokeOneTotal<pokeTwoTotal){
setPokemonWinner(pokemonName2);
}else{
setPokemonWinner("Draw");
}
}, [pokeOneTotal, pokeTwoTotal])
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
// Whatever we want to do after the state has been updated.
}, [state])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "state" in the example is interchangeable with whatever state piece you're dealing with.
Check the documentation for more info.
Either:
Pass the new values to selectedWinner as arguments instead of reading from the state.
Move the call to selectedWinner into a separate useEffect hook that has those state variables as dependencies (so it gets called when, and only when, any of them change).

'Calling' UseEffect from another 'UseEffect'

I'm learning react native and I'm programing a simple app to register the time of sleep of each day.
When the button that add the new register is pressed I do this
onPress={() => setUpdateAction(true)}
That changes the value of the updateAction:
const [updateAction, setUpdateAction] = useState(false);
When the value of updateAction is changed this will be executed:
useEffect(() => {
... code that add's the register to an array
setviewInfoAction(true);
setUpdateAction(false);
}, [updateAction]);
And inside I call setviewInfoAction(true); becouse I want to change the value that is showed with the value that was inserted.
const [viewInfoAction, setviewInfoAction] = useState(false);
useEffect(() => {
console.log("CALLED");
var seted = false;
for (var i = 0; i < registSleep.length; i++) {
if (
registSleep[i].day === selectedDay &&
registSleep[i].month === selectedMonth &&
registSleep[i].year === selectedYear
) {
setSelectedDayHours(registSleep[i].hours);
seted = true;
}
}
if (!seted) {
setSelectedDayHours(0);
}
setviewInfoAction(false);
}, [viewInfoAction]);
Doing this I was expecting for the second UseEffect to executed but it's not...
The way you have your useEffect set up it will only ever re-run if selectedDay changes. If you want to make it run when setInfoViewAction is executed add viewInfoAction into the dependencies.
Even better because all of this is related logic to the same sideEffect. I would simplify your code by removing the second useEffect and adding the code inside it into the first useEffect. This is mainly just because you can keep all related side effect logic together.

Using value returned from function in react component (auto-update)

I wish to use the value returned from this function:
const quantity = () => {
let cookies = document.cookie.split("?");
return cookies.length;
}
in my react component:
const cart_button = (
<Mfont>{quantity}</Mfont>
);
<Mfont> is a standard span element styled with styled-components;
console.log(cookies.length)
gives me a number based on its length as expected, but I can't figure out how to insert it to my component and update every time a cookie.length increases.
According to this answer, the most reliable way still in 2019 to detect changes in cookies is to check document.cookie on interval.
In React 16.8, we have useEffect hook that we could use together with useState hook to run a function on interval and cause the component to update when the value of document.cookie has been updated, like
import React, { useState, useEffect } from 'react';
const MyComponent = props => {
const [latestCookie, setLatestCookie] = useState(document.cookie);
useEffect(() => {
const detectCookieUpdate = document.cookie !== latestCookie && setLatestCookie(document.cookie);
const interval = window.setInterval(detectCookieUpdate, 1000);
return () => window.clearInterval(interval);
});
return (
<Mfont>{quantity()}</Mfont>
);
};
This way we run a function every 1 second that checks if the value of document.cookie is equal to the previous value. Since the value is always a string, it's safe to use strict comparison operator === and not do any checks on top of that. When the values don't match, we run setLatestCookie provided by the state hook, causing the component to render again, and therefore making use of quantity function that will run again.
Have you tried to pass length value as props to your component? Then you can assign that value to the component state. When state changes do whatever you want.

Categories

Resources