React hook, accessing state value directly after updating it - javascript

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)

Related

Set timer for dependency array value useEffect

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

React functions not happening in order [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
The problem I'm having is that this set of code NEEDS to happen in order. It is all packaged inside a component that is updating every second.
Here is what I want to happen when the component mounts:
Step 1: On load, retrieve the last known timestamp stored in local storage and subtract it by new Date() /1000 then console log the answer.
Step 2: retrieve all data from local storage and update state (this includes the timestamp), continuing this process every second.
As it stands in my code, step 2 is happening first.
Here's a video of the app I'm working with and the component that's updating every second to provide context to my issue. I highlight my console log being 0. This is the issue I want to fix. I need the console log to not give me 0, but the current timestamp - the previous timestamp. This is so if a user using my app goes offline and comes back, it counts the time they were gone.:
https://www.youtube.com/watch?v=N0tOZhHfio4
Here's my current code:
const exampleComponent = () => {
const d = new Date() /1000
const timeStamp = () => {
return Math.round(d)
}
const idler = () => {
return timeStamp() - Lockr.get('timeStamp')
}
window.onload = (event) => {
console.log(idler())
}
saveLockr(state)
useEffect(() => {
const timer = setInterval(() => {
setState((state) => ({
minions: state.minions,
counter: state.counter + state.minions + (state.minionSupervisors*3) + (state.minionManagers * 9) +
(state.grandmas * 18) +
(state.dads * 36) +
(state.aliens * 72) +
(state.angels * 144),
minionSupervisors: state.minionSupervisors,
minionManagers: state.minionManagers,
grandmas: state.grandmas,
dads: state.dads,
aliens: state.aliens,
angels: state.angels,
}));
}, 1000);
return () => {
clearInterval(timer);
};
// eslint-disable-next-line
},[]);
}
The SaveLockr hook code:
const saveLockr = (state) => {
const d = new Date() /1000
const timeStamp = () => {
return Math.round(d)
}
Lockr.set("counter", state.counter);
Lockr.set("minions", state.minions);
Lockr.set("minionSupervisors", state.minionSupervisors);
Lockr.set("minionManagers", state.minionManagers);
Lockr.set("grandmas", state.grandmas);
Lockr.set("dads", state.dads);
Lockr.set("aliens", state.aliens);
Lockr.set("angels", state.angels);
Lockr.set('timeStamp', timeStamp())
};
A few things. I don't know exactly what you are doing, but you have to know that Javascript is an asynchronous programming language. That means that stuff doesn't happen in order. If a function takes time, Javascript will go onto the next function. It won't wait for the first function to finish to then go to the second function. This makes Javascript very efficient but adds a little more difficulties to the user. There are a few ways to go around this. I have linked articles to three methods.
Callbacks:
https://www.w3schools.com/js/js_callback.asp
Promises:
https://www.w3schools.com/js/js_promise.asp
Async/Await:
https://www.w3schools.com/js/js_async.asp
You can choose whichever you like depending on your circumstances. Also there is something wrong with your code on the last line.
Lockr.set('timeStamp', timeStamp());
You are passing in the timestamp function. When you do this remove the () from the timestamp. Anyways, I hope this helped.
I DID IT I DID IT I DID IT!
Here's the answer to my problems:
useEffects! Let's go more in depth though.
instead of window.onload to run my code when the component mounts, we use
useEffect(() => {
console.log(idler())
},[])
the [] gives us an exit so that the useEffect doesn't trigger every time the component updates.
We still have to clean up my saveLockr(state) hook though. Let's toss that in a useEffect as well!
useEffect(() => {
saveLockr(state);
})
This time there's no [] so that it will save state every time my component updates.
useEffects keep everything happening in order so I can get my desired results!
The confusion was caused by my lack of understanding on what the [] even do in a useEffect so I ended up not using it, or using it incorrectly in my question.

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.

Cloud Function stuck in an infinite loop

exports.addNewValue = functions.database.ref('/path')
.onWrite(event => {
event.data.adminRef.update({"timeSnapshot":Date.now()})})
It appears that Date.now() causes an infinite loop in the function because the following does not:
exports.addNewValue = functions.database.ref('/path')
.onWrite(event => {
event.data.adminRef.update({"timeSnapshot":"newString"})})
How do I fix this?
If you write back to the same location in the database that was previously changed, you can expect this sequence of events:
Function is triggered with the first change from the client
Function writes back to the database
Function is triggered a second time because of the write during step #2
All writes to the database that match the filter path, even those from within the same function, will trigger the function.
In step 3, you need a strategy to figure out if the second function invocation should result in yet another write back to the database. If it does not require another write, the function should return early so that it won't trigger another write. Typically you look at the data in the event passed to the function and figure out if it was already modified the first time. This could involve looking to see if some flag is set in the database, or if the data you modified does not need any more changes.
Many of the code samples provided by the Firebase team do this. In particular, look at text moderation. Also, there is a video that describes the problem and a possible solution. In the end, you're responsible for coming up with the strategy that meets your needs.
I think the following should work fine :-
exports.addNewValue = functions.database.ref('/path/timeSnapshot')
.onWrite(event => { event.data.adminRef.set(Date.now()) })
The logic behind the above is that when you put a trigger function on a higher node (such as /path in your case), then the function would be fired each time a change is made to any of its child nodes (/timestamp in your case) - hence, the infinite loop.
Therefore, as a general practice, for efficiency as well as cost effectiveness, make sure that your trigger function has the lowest possible path node. Flatting out your data really helps in this as well.
If you arrived here having problems with querying try using .once('value') ... it will mean that you only look at the reference point once ... i.e.
ref.orderByChild("isLive").equalTo(true).once("value" , function(snapshot) {
instead of
ref.orderByChild("isLive").equalTo(true).on("value", function(snapshot) {
as the second will have you listening all the time, and when data changes at the ref, the listener will receive the changes and run the code inside your block again

Categories