Set timer for dependency array value useEffect - javascript

In my React Native app, i'm using a dependency array in my useEffect and want to rerender when the value changes. But i want the rerendering to happen after 2 or 3 seconds. Currently it rerenders instantly which is adding some flickering issue in my app. Now, here's my useEffect currently:
useEffect(() => {
fetchClimate();
setCount(count + 1);
if (rooms.length >= 1 && count < 2 && displayRoomList) {
setActive(rooms[0].id);
}
const interval = setInterval(() => {
fetchClimate();
}, 10000);
return () => clearInterval(interval);
}, [rooms]);
Whenever the value of room changes from a button press, useEffect is called instantly. I want this to be called after 2 or 3 seconds of when the room data changes. How can i do that?

It's most likely the wrong solution to purposefully wait several seconds as you're probably creating new race conditions. If you really want to do that you would need to run the code that produces the change in the UI in a setTimeout for 3000 ms.
https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
setTimeout(() => {console.log("Code here runs after 3 seconds")}, 3000);
However, the real answer is that you need to solve for why the instaneous reload looks bad. Forcing your code wait is likely to introduce race conditions (causing more difficult to solve bugs) and creates a poor user experience. You might be able to get away with it this time, but as soon as you have another similar problem this solution becomes untenable. Other Possible solutions:
If your code is waiting for another asynchronous change you could to add that as a dependency to the useEffect and place the UI changing code in an if statement.
If the layout update itself looks jarring, try using React Native's LayoutAnimation. This automatically animates layout changes but can be kind of hit or miss. https://reactnative.dev/docs/layoutanimation
if (
Platform.OS === "android" &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
useEffect(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
//Run UI changing code
}, [rooms])
And finally if you're having issues with your code running before other layout changes are completed, you could try changing useEffect to useLayoutEffect
https://reactjs.org/docs/hooks-reference.html#uselayouteffect
Ultimately, there is almost always a better solution than using setTimeout

Related

Svelte tick() not forcing updates to variables

I have run into what seems like a common problem in svelte but haven't found a good solution. I have a function that is called when a button is pressed in a component, the basic situation is shown below.
<script>
import tick from 'svelte'
let x = 0;
let y = 0;
let z = 0;
async function buttonHandler() {
--- multiple computationally intensive loops
that update the vars x,y,z at different points ---
e.g.
for (a in arr) {
x ++;
await tick();
}
}
</script>
<main>
<button on:click={buttonHandler}>Click Me</button>
<p>{x}</p>
<p>{y}</p>
<p>{z}</p>
</main>
I basically wanted the x, y, z values to update on screen as the function incremented them, to have live updates of the progress. Originally they were not updating until the function completed, and I read that it was because svelte batch updates changes to reactive variables. I then read that the 'tick()' function was designed for this situation, to 'flush' the updates and have the DOM rerender the updated values. However this still isn't working, the only way I can get it work is by replacing tick() with a generic sleep() function:
function sleep(millisec = 0) {
return new Promise((resolve, reject) => {
setTimeout(_ => resolve(), millisec);
});
};
This doesn't seem like its a good solution, and I feel like I must be missing something. Have any others had situations where tick isn't working as expected?
You can use the afterUpdate function to check that you flush as expected
import { tick, afterUpdate } from 'svelte';
afterUpdate(() => {
//This should log multiple times, one for every expected value change after tick()
console.log(x, y ,z);
})
I tried your code in REPL, added basic loops that update every 100x loop, and it still looks instant, but the logs show it did update correctly. So the flush does not trigger a new render like in React (blocks the thread), instead, it has its own batched schedule. To work around this and keep things reactive, and not looking clunky they suggest using CSS animations to get the desired smooth effect. CSS animations don't block the main thread.
"When you update component state in Svelte, it doesn't update the DOM
immediately. Instead, it waits until the next microtask to see if
there are any other changes that need to be applied, including in
other components. Doing so avoids unnecessary work and allows the
browser to batch things more effectively."
from: (https://svelte.dev/tutorial/tick)[https://svelte.dev/tutorial/tick]
So this is why it looks instant in your case.
You can see here how they solved this with a tween animation:
https://svelte.dev/tutorial/tweened
So they slow things down with animations which does NOT slow down or block the main thread like setTimeout, setInterval or a dom render does.
UPDATED
better example here: https://svelte.dev/repl/c2856360456c40e98ace08438e5bf82f?version=3.38.2

React hook, accessing state value directly after updating it

I know this has been asked a lot and in many different ways but I couldn't find something that works for me. The functionality I want in my code is this
const TestApp() => {
const [count, setCount] = useState(null);
const updateAndShowCount = () => {
setCount(count + 1);
// This console.log should be happen after setCount() has completed it's job
console.log(count);
}
}
The function will be called by a button onClick effect. What it currently does is to update count and print the value of count before the update.
I want the button press to update count and immediately "after" print the updated value.
This is a test case for achieving what I want, in my actual project the function will receive a value from an input field and then make a get request using that value (with axios).
I am assuming that the answer to this simple test case will work in my case as well, and since this asynchronous behaviour is something I don't quite understand yet I thought it would be sensible to get a grasp of the general concept first.
From what I have gathered though, I think I should mention that useEffect() is already being used for an initial get request, so if your answer involves using that you should assume that I don't know how to add something to useEffect() while retaining it's current functionality.
The current useEffect():
useEffect(() => {
axios.get("mybackend/").then(res) => {
setRequestData(res.data);
});
}, []);
My actual question is in bold for clarity. Any info regarding the situation as a whole is welcome as well. Thanks for reading!
The solution follows naturally when adding count to your request (I guess it's some query parameter of some sort), because as you then use count inside the effect, it is a dependency of the effect, thus whenever the dependency changes, the effect will run again:
useEffect(() => {
axios.get("mybackend?count=" + count).then(res) => {
setRequestData(res.data);
});
}, [count]);
Note that the effect is still missing a cleanup callback, which would cancel an ongoing request, so that when count changes again while the request is still ongoing, not two requests run at the same time concurrently.
i'am not sure that i understood your question completely, but i think the answer is: you should use the callback in your setState, like that:
setCount(count => count + 1)

React call back on set state

index.js:1 Warning: State updates from the useState() and useReducer()
Hooks don't support the second callback argument. To execute a side
effect after rendering, declare it in the component body with
useEffect().
This is the error I get when trying to do this
setUnansweredQuestions({ newUnansweredQuestions }, () =>
nextQuestion()
);
I tried run a function after updating the state for unanswered questions but won't work cause it doesn't update right away.
I searched a bit and it is said to use useEffect but I already have one defined and won't let me create another. I just want to call the function nextQuestion after updating UnansweredQuestions
useEffect(() => {
setUnansweredQuestions(questions);
selectRandomQuestion();
}, [currentQuestion]);
There's nothing wrong with having multiple useEffects.
Since you do setUnansweredQuestions and want to run something after that state variable changes, just do:
useEffect(nextQuestion, unansweredQuestions);
You can try like this, I know this is not a good solution, but it works
setState(UnansweredQuestions);
setTimeOut(() => {
nextQuestion()
}, 16)

React Performance Issues in Firefox?

I'm experiencing some performance issues with a react application that I developed. These issues specifically (or most notably) occur with Firefox (both FF developer 77.0b7 and FF 76.0.1).
When using this application in Firefox, CPU usage gets extremely high, and my fans start spinning up to very high speeds. I get about 15-19fps in firefox according to the performance tools in FF. I get roughly 60fps in Chrome and Safari.
These issues occur when I begin typing into the input field, and get worse as the input gets longer (which makes sense)
The application is available here:
https://text-to-aura-generator.netlify.app/
Source code available here: https://github.com/paalwilliams/Text-to-Aura/tree/master/src
I'm almost certain that this is something I'm doing incorrectly, or that I've written the code inefficiently, but that isn't necessarily supported by the stark performance difference between browsers. Is chrome just that much better and handling react/constant rerenders?
I know that this is a broad question, but I honestly don't understand what is happening here, or necessarily how to troubleshoot it beyond the developer tools. Any input or thoughts would be greatly appreciated.
The problem is your application is rendering too fast. In your particular case, there a few ways to improve that.
Every time you update the state, React needs to re-render your application, so updating the state within a loop is usually a bad idea.
Also, you are using useState 3 times, but only colors should be there, as App actually needs to re-render to reflect the changes there. The other two pieces of state (text and hex) are only being used to pass data from the handleChange to the callback inside useEffect.
You can restructure your code to:
Avoid updating the state within a loop.
Use a simple variable instead of state.
Use useCallback to define a function with that logic that is not re-created on each render, as that forces TextInput to re-render as well.
Throttle this callback using something like this:
import { useCallback, useEffect, useRef } from 'react';
export function useThrottledCallback<A extends any[]>(
callback: (...args: A) => void,
delay: number,
deps?: readonly any[],
): (...args: A) => void {
const timeoutRef = useRef<number>();
const callbackRef = useRef(callback);
const lastCalledRef = useRef(0);
// Remember the latest callback:
//
// Without this, if you change the callback, when setTimeout kicks in, it
// will still call your old callback.
//
// If you add `callback` to useCallback's deps, it will also update, but it
// might be called twice if the timeout had already been set.
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Clear timeout if the components is unmounted or the delay changes:
useEffect(() => window.clearTimeout(timeoutRef.current), [delay]);
return useCallback((...args: A) => {
// Clear previous timer:
window.clearTimeout(timeoutRef.current);
function invoke() {
callbackRef.current(...args);
lastCalledRef.current = Date.now();
}
// Calculate elapsed time:
const elapsed = Date.now() - lastCalledRef.current;
if (elapsed >= delay) {
// If already waited enough, call callback:
invoke();
} else {
// Otherwise, we need to wait a bit more:
timeoutRef.current = window.setTimeout(invoke, delay - elapsed);
}
}, deps);
}
If the reason to use useEffect is that you were not seeing the right values when updating colors, try using the version of setState that takes a callback rather then the new value, so instead of:
setColors([...colors, newColor]);
You would have:
setColors(prevColors => ([...prevColors , newColor]));
The most common performance issues with react come from setting the state too many times since you're constantly re rendering the page and the elements within it.

how to redirect a react component on set timeout

I have a quiz application that I am creating and I have a Question component. Each time the question component renders, I want to start a 60 second timer. If the user does not answer or leaves the application, after 60 seconds I want to redirect them to the home page where the next user would be able to start the quiz from the beginning.
I also had another question about the componentWillMount() function. I have that same question componenent that has 6 different instances or ('questions') as you would say. When i go from question 1 to question 2, the componentWillMount() function does not refire. It only fires on the first render of that component, even though they are different instances. I need to make sure that timer restarts on each components rendering.
What would be the best way of doing this? Is it best to use the props.history.push('/') .
componentWillMount(){
setTimeout(() => {
console.log('this ran')
this.props.history.push('/');
}, 60000)
}
Not sure, but maybe there is a problem with setTimeout which purpose is to trigger event once, so you can try with setInterval which purpose is execute function unlimited number of times.
Maybe you can check more here.

Categories