Using React states with delay - javascript

I am using a library called react-easy-state for my state management but I believe this questions is not directly related to this library rather a general question.
Having a code like this in my App.js :
function App() {
const [isChanged, setIsChanged] = React.useState(false);
console.log(store.counter);
React.useEffect(() => {
setTimeout(() => {
setIsChanged(true);
}, store.counter);
}, []);
return (
<div className="App">
{isChanged && <h1>Hello CodeSandbox</h1>}
<Change />
</div>
);
}
Which has a child <Changed /> that looks like this :
export default function Change() {
React.useEffect(() => {
store.rndNum = ~~(Math.random() * 6) + 11;
}, []);
store.counter = ~~Math.pow(1.555, store.rndNum) * 20;
The child is supposed to generate a random number and then modify the random number and store it in another variable in my store. The modified value is supposed to slow down the appearance of an HTML element which toggled and triggered by setTimeout. However my console shows that number that is being generated is not matching with my code and as it generates 20 for some reason and then proceeds to generate the correct number. In the mean time I would like to wait until the value in my store in updated and then run setTimeout. Whereas currently setTimeout takes either 0 or 20 as the timer and executes the code without any delay.
code to reproduce : https://codesandbox.io/s/hopeful-cdn-vg7kh

You are getting 20 first because of this operation.
store.counter = ~~Math.pow(1.555, store.rndNum) * 20;
Why? Because store.rndNum at that time is still "" (declared on store as "") and this part
~~Math.pow(1.555, store.rndNum)
will always be 1.
Hooks are asynchronous, so useEffect will not change the rndNum value first.
Edit for my comment:
React.useEffect(() => {
if (!isNaN(store.rndNum)) {
store.counter = ~~Math.pow(1.555, store.rndNum) * 20;
}
}, [store.rndNum]);

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()

'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.

Understanding Svelte tick() lifecycle

I'm halfway through the Svelte docs and I have a hard time understanding the tick() lifecycle. Is there an alternative to it in React?
For example, what it actually does in this example from the tutorial?
<script>
import { tick } from 'svelte';
let text = `Select some text and hit the tab key to toggle uppercase`;
async function handleKeydown(event) {
if (event.which !== 9) return;
event.preventDefault();
const { selectionStart, selectionEnd, value } = this;
const selection = value.slice(selectionStart, selectionEnd);
const replacement = /[a-z]/.test(selection)
? selection.toUpperCase()
: selection.toLowerCase();
text = (
value.slice(0, selectionStart) +
replacement +
value.slice(selectionEnd)
);
await tick();
this.selectionStart = selectionStart;
this.selectionEnd = selectionEnd;
}
</script>
<style>
textarea {
width: 100%;
height: 200px;
}
</style>
<textarea value={text} on:keydown={handleKeydown}></textarea>
When you update your variable, it does not immediately reflects onto the DOM, it's batched together with other changes and updates in the next update cycle.
Much like react, if you do
this.setState({ foo: 123 });
this.state.foo // you don't expect to read 123
// because this.setState is asynchronous!
Let's take a look at 1 example, and see how we do it in react vs svelte
say you have a growing text input, which as you type, it should grow in height, to accommodate more text.
Here's probably how you'd do it in React:
function GrowingTextInput() {
const ref = useRef();
const [value, setValue] = useState('');
const updateText = (newValue) => {
setValue(newValue);
// NOTE: you can't measure the text input immediately
// and adjust the height
// you do it in useEffect
}
useEffect(() => {
calculateAndAdjustHeight(ref.current, value);
}, [value]);
return <textarea value={value} ref={ref} />
}
you use useEffect to do adjustment on the DOM elements, after the state update has applied to the DOM.
In svelte, this is how you'd do it:
<script>
let ref;
let value;
const updateText = async (newValue) => {
value = newValue;
// NOTE: you can't measure the text input immediately
// and adjust the height, you wait for the changes to be applied to the DOM
await tick();
calculateAndAdjustHeight(ref, value);
}
</script>
<textarea bind:this={ref} {value} />
In svelte, to wait for the state changes applied, you wait for the promise returned from tick() to be resolved.
I've tried to explained it in a youtube video, if you prefer something more visual:
https://youtu.be/JjONMdhaCDs
Also, here' are my take on what other use cases that you may use tick():
https://dev.to/tanhauhau/2-amazing-use-case-of-tick-in-svelte-that-you-must-know-8pa
stuff you write after tick in svelte is the same as writing that in The second parameter to setState(). the second argument of setState is an optional callback function that will be executed once setState is completed and the component is re-rendered.
// change the state
await tick() // wait til the state is actually changed
// do x
// do y
In React it would be :
this.setState(..., () => {
// do x
// do y
})
I don't think so. From what I can tell it appears to be a way to queue up a value or continue function logic for use in the the "next render cycle". React's model is to collect all state/prop changes from the current render cycle, compute the next rendered output, and commit the change, thus starting the next render cycle.
Closest you get with React is this.setState with class-based components, or the useState hook for functional components.

How to reset the timer component upon completion

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.

Categories

Resources