I have a form which, when I submit, should call up 2 functions one by one and then run a condition that is dependent on the mentioned 2 functions.
The handler starts when you press the button, but how do you ensure that they will run sequentially and wait for the result of the previous one?
const handlerNextPage = () => {
controlValidInputs();
handlerSetValues();
inputsData.pages.isValid.page1 && navigate("step2");
};
Thank you
It depends from the nature of your functions and what you are doing inside of them.
If they execute all synchronous tasks, they will be called sequentially and will execute sequentially one after another, that's due to the synchronous single threaded nature of JavaScript engine Event Loop.
BUT
If inside one of those functions you are performing some asynchronous task, like an http fetch or a setTimout, the next function will be executed while the previous one async operations have not been performed yet. To handle those cases you need to use promises.
In your case I don't think you have any asynchronous task in your first function controlValidInputs() and in the second one I assume you are performing some React setState, the React setState is asynchronous but can't be handled with promises, you need to use the useEffect hook to defer operations after a react state update.
if they are synchronous functions so you don't need to worry about they will run one by one but if you are setting state which is asynchronous or calling API in the function which I see that's not the case here. But if you still call an API here in the function you can handle in quite easily by making the function into an async function and putting await but if you setstate and use it value right that is the tricky part because setState need little bit time before setting it so you need to update your logic little bit suppose handlerSetValues(); you setState in this function like this:
const handlerSetValues = () => {
// code here
// code here
// get value of state
setState(value) // this take little bit time so
return value // as well and use it handlerNextPage function
}
const handlerNextPage = () => {
controlValidInputs();
const value = handlerSetValues();
// now use value as state like you use below code I think.
inputsData.pages.isValid.page1 && navigate("step2");
};
you can useeffect
useEffect(()=>{
// inputsData.pages.isValid.page1 && navigate("step2");
},[depency-OnChanging-Which-Call-Code-Inside-Effect])
Related
i'm woundring if is it OK to write all the logic inside a useEffect react hook or to create a function and call it inside the useEffect.
First way
useEffect(()=>{
// code logic
},[dependency array])
Second way:
const toDoFunc = ()=>{
// code logic
}
useEffect(()=>{
toDoFunc()
},[dependency array])
i'm really confused cause i tested both approaches in matter of time execution(using the console.time && console.timeEnd functions ) and found that sometimes the first approache is faster and sometimes the second one is faster.
There is no significant difference in terms of performance. If your function is only used inside your useEffect you should place it there. Otherwise you would need to declare the function as a dependency and stabilize its reference, or use the upcoming useEvent hook. If your function is not reactive, you could also put it outside the component.
I have a useEffect function that must wait for four values to have their states changed via an API call in a separate useEffect. In essence the tasks must happen synchronously. The values must be pulled from the API and those stateful variables must be set and current before the second useEffect can be called. I am able to get the values to set appropriately and my component to render properly without doing these tasks synchronously, I have a ref which changes from true to false after first render (initRender), however I find the code to be hacky and inefficient due to the fact that the second useEffect still runs four times. Is there a better way to handle this?
//Hook for gathering group data on initial page load
useEffect(() => {
console.log("UseEffect 1 runs once on first render");
(async () => {
const response = await axios.get(`${server}${gPath}/data`);
const parsed = JSON.parse(response.data);
setGroup(parsed.group);
setSites(parsed.sites);
setUsers(parsed.users);
setSiteIDs(parsed.sitesID);
setUserIDs(parsed.usersID);
})();
return function cleanup() {};
}, [gPath]);
//Hook for setting sitesIN and usersIN values after all previous values are set
useEffect(() => {
console.log("This runs 4 times");
if (
!initRender &&
sites?.length &&
users?.length &&
userIDs !== undefined &&
siteIDs !== undefined
) {
console.log("This runs 1 time");
setSitesIN(getSitesInitialState());
setUsersIN(getUsersInitialState());
setLoading(false);
}
}, [sites, siteIDs, users, userIDs]);
EDIT: The code within the second useEffect's if statement now only runs once BUT the effect still runs 4 times, which still means 4 renders. I've updated the code above to reflect the changes I've made.
LAST EDIT: To anyone that sees this in the future and is having a hard time wrapping your head around updates to stateful variables and when those updates occur, there are multiple approaches to dealing with this, if you know the initial state of your variables like I do, you can set your dependency array in a second useEffect and get away with an if statement to check a change, or multiple changes. Alternatively, if you don't know the initial state, but you do know that the state of the dependencies needs to have changed before you can work with the data, you can create refs and track the state that way. Just follow the examples in the posts linked in the comments.
I LIED: This is the last edit! Someone asked, why can't you combine your different stateful variables (sites and sitesIN for instance) into a single stateful variable so that way they get updated at the same time? So I did, because in my use case that was acceptable. So now I don't need the 2nd useEffect. Efficient code is now efficient!
Your sites !== [] ... does not work as you intend. You need to do
sites?.length && users?.length
to check that the arrays are not empty. This will help to prevent the multiple runs.
2021 UPDATE
Use a library that makes requests and cache them - react-query, swr, redux-toolkit-query
ORIGINAL QUESTION
I've been struggling with this for quite a long time and didn't find an answer.
I have a component that is the last step of some registration process during which I ask a user to enter its data through several forms. In this component, I send collected data to API. If the request is successful I show ok, if not I show error.
I have useEffect that sends the data. The function that performs this task lives in a context
const { sendDataToServer } = useContext(context)
useEffect(() => {
const sendData = async () => {
setLoading(true)
await sendDataToServer(...data)
setLoading(false)
}
sendData()
}, [sendDataToServer, data])
If I include sendDataToServer in the dependencies list this useEffect would go into an infinite loop, causing endless rerendering. I suppose this is because a reference to the function has a different value on every render.
I can of course redesign the app and do not keep the function in the context, but I do like it and don't consider it a bad practice (correct me if I am wrong)
So what are my options here? How do I keep the flow with the context API, but use useEffect with the correct list of dependencies?
You're right with your guess, that's why we got useCallback for referential equality.
You didn't post the sendDataToServer function, but it should look something like this with useCallback:
const sendDataToServer = useCallback(data => {
// your implementation
}, [your, dependencies])
After that you can safely use it in your useEffect.
I highly recommend Kent C. Dodd's blog posts: When to useMemo and useCallback
Smartassing now: If it's only purpose is sending data to the server (and not changing the app's state), I don't know why it should be part of the context. It could be a custom hook or even a static function.
Btw: There could be another problem: If the data dependency in your useEffect is changed when executing sendDataToServer, you will still have an endless loop (e. g. when you fetch the new data after executing sendDataToServer), but we can't see the rest of the code.
I try to reselect and immediately got up the problem: data polling works. Almost every time the same data arrives in the reducer — a component rerender occurs using this data. I try to cache this data with the selector — fails — the rerender still happens. What is wrong with this code?
function getAllTickets(reducer) {
return reducer.get('tickets');
}
export const allTicketsSelector = createSelector([getAllTickets], items => items);
By default, the surface comparison function is used in createSelector. Want to make a deep comparison, use createSelectorCreator and your comparison function. There is an example in the documentation.
I have a problem and I'm pretty sure I'm not the only one who ever had it... Although I tried to find a solution, I didin't really find something that fits my purpose.
I won't post much code, since its not really a code problem, but more a logic problem.
Imagine I have the following hook:
useEffect(() => {
fetchFromApi(props.match.params.id);
}, [props.match.params.id]);
Imagine the result of fetchFromApi is displayed in a simple table in the UI.
Now lets say the user clicks on an entity in the navigation, so the ID prop in the browser URL changes and the effect triggers, leading to an API call. Lets say the call with this specific ID takes 5 seconds.
During this 5 seconds, the user again clicks on an element in the navigation, so the hook triggers again. This time, the API call only takes 0,1 seconds. The result is immediatly displayed.
But the first call is still running. Once its finished, it overwrites the current result, what leads to wrong data being displayed in the wrong navigation section.
Is there a easy way to solve this? I know I can't cancel promises by default, but I also know that there are ways to achieve it...
Also, it could be possible that fetchFromApi is not a single API call, but instead multiple calls to multiple endpoints, so the whole thing could become really tricky...
Thanks for any help.
The solution to this is extremely simple, you just have to determine whether the response that you got was from the latest API call or not and only then except it. You can do it by storing a triggerTime in ref. If the API call has been triggered another time, the ref will store a different value, however the closure variable will hold the same previously set value and it mean that another API call has been triggered after this and so we don't need to accept the current result.
const timer = useRef(null);
useEffect(() => {
fetchFromApi(props.match.params.id, timer);
}, [props.match.params.id]);
function fetchFromApi(id, timer) {
timer.current = Date.now();
const triggerTime = timer.current;
fetch('path').then(() => {
if(timer.current == triggerTime) {
// process result here
// accept response and update state
}
})
}
Other ways to handle such scenarios to the cancel the previously pending API requests. IF you use Axios it provides you with cancelToken that you can use, and similarly you can cancel XMLHttpRequests too.