React functions not happening in order [closed] - javascript

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.

Related

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)

How to debounce Lodash call to fetch in Typescript properly?

I'm relatively new to React & Typescript/JS and am trying to understand how to debounce a call to a promise properly. There's a button to increment/decrement a desired purchase quantity and only after a certain period of time do I want to actually execute the call to the backend API, to avoid the user just repeatedly hitting the +/- buttons and spamming the server.
import debounce from 'lodash/debounce'
const [isQueryingPrice, setIsQueryingPrice] = useState(false);
const [desiredPurchaseQty, setDesiredPurchaseQty] = useState<number>(1);
const changeQty = (delta: number) => {
setDesiredPurchaseQty(desiredPurchaseQty + delta);
setIsQueryingPrice(true);
debounce(fetchPrice, 1000)();
}
const incrementQty = (event: any) => {
changeQty(1);
}
const decrementQty = (event: any) => {
changeQty(-1);
}
const fetchPrice = (qty: number) => {
// not sure that arrow function wrapping fetch is the best approach here?
// some imaginary api
fetch("https://api.example.com/get-price?qty=" + qty).then((response) => {
return response.json();
});
}
// in my component:
< div>
<Button onClick={decrementQty}>Dec</Button>
<Button onClick={incrementQty}>Inc</Button>
</div>
However, whilst the increment/decrement works, the debounced function is fired multiple times when the timer expires. I want to have a single promise executed when the user has stopped pressing the change quantity buttons, and I want the promise execution to respect the count, i.e. if the user presses the count button 3 times, such that the total is 4, then the call to get the price should be for a quantity of 4.
I think this might be that the calls to change state cause a re-render and a creation of a new debounced function and I think perhaps the calls to the debounce function don't cause the old scheduled callbacks to be cleared. However, this is where my knowledge of JS starts to break down...
I'd also like to know how the approach should work for an initial load of price: I think there's a call inside a useEffect() to get the price when the page first loads.
You don't want to create a new debounced function every time you change quantity and certainly not on every render.
Here I've wrapped the original fetchPrice with the debounce function and also saved state so we can use the same debounced function across renders.
const [debouncedFetchPrice] = useState(() => debounce(fetchPrice, 1000));
and then in changeQty function you should call debouncedFetchPrice instead of fetchPrice
This should mean it will trigger with the latest desiredPurchaseQty value after a 1000 millis delay.
Furthermore...
I've found this article useful in the past to create a useDebounce hook with useEffect. Check it out
https://dev.to/gabe_ragland/debouncing-with-react-hooks-jci

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.

Are JavaScript event loop operations on variables blocking?

In the non-blocking event loop of JavaScript, is it safe to read and then alter a variable? What happens if two processes want to change a variable nearly at the same time?
Example A:
Process 1: Get variable A (it is 100)
Process 2: Get variable A (it is 100)
Process 1: Add 1 (it is 101)
Process 2: Add 1 (it is 101)
Result: Variable A is 101 instead of 102
Here is a simplified example, having an Express route. Lets say the route gets called 1000 per second:
let counter = 0;
const getCounter = () => {
return counter;
};
const setCounter = (newValue) => {
counter = newValue;
};
app.get('/counter', (req, res) => {
const currentValue = getCounter();
const newValue = currentValue + 1;
setCounter(newValue);
});
Example B:
What if we do something more complex like Array.findIndex() and then Array.splice()? Could it be that the found index has become outdated because another event-process already altered the array?
Process A findIndex (it is 12000)
Process B findIndex (it is 34000)
Process A splice index 12000
Process B splice index 34000
Result: Process B removed the wrong index, should have removed 33999 instead
const veryLargeArray = [
// ...
];
app.get('/remove', (req, res) => {
const id = req.query.id;
const i = veryLargeArray.findIndex(val => val.id === id);
veryLargeArray.splice(i, 1);
});
Example C:
What if we add an async operation into Example B?
const veryLargeArray = [
// ...
];
app.get('/remove', (req, res) => {
const id = req.query.id;
const i = veryLargeArray.findIndex(val => val.id === id);
someAsyncFunction().then(() => {
veryLargeArray.splice(i, 1);
});
});
This question was kind of hard to find the right words to describe it. Please feel free to update the title.
As per #ThisIsNoZaku's link, Javascript has a 'Run To Completion' principle:
Each message is processed completely before any other message is processed.
This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it may be stopped at any point by the runtime system to run some other code in another thread.
A downside of this model is that if a message takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make message processing short and if possible cut down one message into several messages.
Further reading: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
So, for:
Example A: This works perfectly fine as a sitecounter.
Example B: This works perfectly fine as well, but if many requests happen at the same time then the last request submitted will be waiting quite some time.
Example C: If another call to \remove is sent before someAsyncFunction finishes, then it is entirely possible that your array will be invalid. The way to resolve this would be to move the index finding into the .then clause of the async function.
IMO, at the cost of latency, this solves a lot of potentially painful concurrency problems. If you must optimise the speed of your requests, then my advice would be to look into different architectures (additional caching, etc).

I can’t seem to access my updated state from within this function

I have this state:
const [fingerprintingIssues, setFingerprintingIssues] = React.useState([])
I have a function that loops through a bunch of data to validate. If there are errors it adds them to fingerprintingIssues with setFingerprintingIssues. That works fine.
But in this method (abbreviated) I’m checking for fingerprintingIssues inside of the method’s complete and it always returns []. I’ve read this is an issue with closures and stale state, but I can’t seem to see how that applies here.
const validateDocument = data => {
const start = moment()
try {
Papa.LocalChunkSize = 10485
return Papa.parse(data, {
complete: () => {
// Log completion only if it’s been 20 seconds or more
const end = moment()
const duration = moment.duration(end.diff(start))
const seconds = Math.ceil(duration.asSeconds())
if (seconds < MIN_PROCESS_TIME) {
setTimeout(() => {
setUploadStep('tagging')
console.log('fingerprintingIssues', fingerprintingIssues)
}, (MIN_PROCESS_TIME - seconds) * 1000)
} else {
setUploadStep('tagging')
}
},
error: () => {
setDocumentError(true)
setDocumentReady(false)
},
})
…
Any suggestions?
I think indeed it is an issue with closure and stale state. When using hooks, you need to be cautious about how you use callbacks.
Your component function is called many times (one for each render). Each call will receive a particular state. You can sort of think of it like that particular state is associated with that particular render. If you create a callback within your render, that callback will exist inside the closure of the render it was created in. The issue is that if your component is re-rendered, the callback still exists in the closure of the old render, and will not see the new state.
One way around this is to use React.useRef, which you can read more about here. Refs hold a value and can be modified and accessed at any time regardless of closure. Note that modifying the ref does not cause your component to be re-rendered.
const fingerPrintingIssues = React.useRef([])
const validateDocument = data => {
// Perform validation
// Now access (refs are accessed by .current)
console.log(fingerPrintingIssues.current)
}
// Modify:
fingerPrintingIssues.current.push(...)
In case you need to re-render your component when modifying the value, you can use combination of useRef() and useState().

Categories