Executing a function only after setState is done - javascript

I'm using openAi API to talk to ChatGPT in my React App,
I want to send the request to ChatGPT after my message is asynchronously added to messages array,
Is there a way to do it ?
function addMessage() {
setMessages([...messages, { writer: "Me", text: "Hi ChatGPT, how are you ?" }]);
// execute this after the asynchronous setState above is over
getResponse();
}
function getResponse() {
getOpenAiCompletions(text, temp).then((response) => {
setMessages([
...messages,
{ writer: "ChatGPT", text: response.data.choices[0].text },
]);
});
}

If you're just wanting the messages to appear in order, you don't need to wait for the re-render. Even if the response comes through instantly (which is very unlikely), the user's messages will still show before the response messages get appended to the array.
Here's a code sandbox that shows this in action.
Note that you should also be using the callback version of setMessages since you're relying on the previous state -
const addMessage = (msg: string) => setMessages((prev) => [...prev, msg]);

Related

React: Component randomly doesn't refresh after state changes

I am working on a CRUD (express, mongoose, mongodb) which is mostly working as expected... except when it comes to rendering the entries after deleting X amount of them.
Not deleting
Sometimes I can delete 10 entries without any issues, until the component is empty, and other times I delete some entries and then the page just stop rending the updated data and enters an infinite loading state in the browser; only when I hard reload the browser the page renders the latest data, yet it does delete the entry , it just freezes it seems!
From React:
useEffect(() => {
queryClient.invalidateQueries(["allUsers"]);
});
const mutation = useMutation({
mutationFn: async (userid) => {
return await axios.delete("/api/deleteuser", { data: { userid: userid } });
},
});
const handleDelete = (userid) => {
mutation.mutate(userid);
navigate("/", { replace: true });
};
From Mongoose
const deleteUser = async (req, res) => {
try {
await newUser.findOneAndDelete({ userid: req.body.userid });
} catch (error) {
return error;
}
};
Tried invalidating the query cache at the delete function but the result is the same. It just happens randomly, as if the request was not fulfilled... in dev tools/network the request is never fulfilled but the data, whenever it does work, is updated.Network/pending
Edit: I'm using a mongoDB free account... I've read sometimes the response times are not the best, perhaps it is the reason?
useEffect without a dependency array is pretty pointless. It will run every time the component is rerendered.
I assume you are using react-query. Try moving your queryClient.invalidate to onSuccess or onSettled in your useMutation config object. This will ensure your query is invalidated only after the mutation is done.
https://react-query-v3.tanstack.com/guides/mutations#mutation-side-effects

How do I deal with multiple setState called in the wrong order?

Let’s imagine a simple component run where the user clicks on a button that shows a popup (setState(P)) and that causes both an HTTP (setState(H)) and WebSocket (setState(W)) server to respond with other data to be shown in the popup. Note that onClick, onHTTPResponse and onWSResponse are simple prop event handlers passed to the component which call setState:
onClick: --setState(P)-->
onHTTPResponse: ---setState(H)-->
onWSResponse: ----setState(W)-->
Now let’s imagine another run where WebSocket is faster than the HTTP response:
onClick: --setState(P)-->
onHTTPResponse: ----setState(H)-->
onWSResponse: ---setState(W)-->
Problem with this run is that the WebSocket setState(W) depends on the data returned by the HTTP response, hence it depends on setState(H) — setState(W) must occur after setState(H).
How would I deal with such situation?
Edit: it seems from the responses that I didn’t explain my problem clearly. onHTTPResponse and onWSResponse handlers are called with the response of each. This is the API I have available. I need to solve the setState call order from the usage perspective.
Note also that the idea of these prop handlers is that they’re called at unpredictable times. The same example could be made with subscribing to an event emitter for whichever data. So answers that involve async/await or promises don’t really make sense: I’m dealing with an event emitter kind of API.
Why don't you call them in the same order that you described?
If sending a message via WebSocket needs data from HTTP request you can wait for the request from HTTP and call send a message via Websocket afterwards.
Just an example;
import React, { useEffect, useState } from "react";
const exampleSocket = new WebSocket(
"wss://www.example.com/socketserver",
"protocolOne"
);
export default function App() {
const [httpData, setHttpData] = useState(null);
const [socketData, setSocketData] = useState(null);
const onClick = () => {
fetchData();
};
const fetchData = () => {
fetch("yourAPI")
.then(response => {
// Examine the text in the response
response.json().then(function(data) {
sendMessage(data);
setHttpData(data);
});
})
.catch(err => {
console.log("Fetch Error :-S", err);
setHttpData(err);
});
};
const sendMessage = data => {
exampleSocket.send(data);
};
useEffect(() => {
exampleSocket.onmessage = evt => {
// listen to data sent from the websocket server
const message = JSON.parse(evt.data);
setSocketData(message);
};
}, []);
return (
<div className="App">
<button type="submit" onClick={onClick}>
Send request
</button>
<div>{JSON.stringify(httpData)}</div>
<div>{JSON.stringify(socketData)}</div>
</div>
);
}
use setState callback
this.setState(p, () => this.setState(H, () => this.setState(W)))
setState callback will be triggered with updated state values, so no need to use async/await.
You can use aysnc await function with await in front of onHTTP function and onWebSocket function. Note that you need to put the code you want to run first above the one you want to run later. Example,
await onHTTP()
await onWebSocket()
They second one will only run when the first one is successful.
Since my question was rather unclear I decided to post a more generic one without using React (but pure JavaScript). It seems that using observables is a way to solve this problem. More details are posted in this other question: How to deal with race conditions in event listeners and shared state?

Best practice when trying to use async code inside an action

I'm working on a React Native with Expo project, right now i'm trying to delete an item from a list inside a local database. Problem is in the action where I send it. Here is my code.
export const eliminatePlace = (placeId) => {
console.log(`Inside the action with placeID ${placeId}`);
return async dispatch => {
console.log('returned');
try {
const dbResult = await deletePlace(placeId);
console.log(dbResult);
dispatch({
type: DELETE_PLACE,
id: placeId
});
} catch (err) {
throw err;
console.log(err);
}
};
}
Somehow the console.log inside the return didn't fire up, my workaraound was this:
export const eliminatePlace = async (placeId, dispatch) => {
try {
console.log(`Trying to eliminate place with ID ${placeId}`);
const dbResult = await deletePlace(placeId);
console.log(dbResult);
dispatch({type: DELETE_PLACE, id: placeId});
} catch (err) {
throw err;
console.log(err);
}
};
Then it did work, but this is not the best practice, any ideas on why the correct way didn't work?
Here is the link to my github repo where you can download the project:
https://github.com/josmontes/rn-places
In case someone needs to see another place of the code please ask, I didn't add anything else so it doesn't bloats the question and because the problem is inside this function.
You shouldn't be calling async functions inside your action creators. You can read more about why here. Instead, you should use async actions. Even though you aren't making an API call, you can still represent your process as request-success-failure. Basically, dispatch a "request" action and as a side effect call your async function. Once it resolves, you dispatch either "success" or "failure" action, depending on the result. You can then put the results from the database in the payload of the "success" action. You can read more about that here.
I believe the the second example you gave works, because that's basically just the "success" action. It only dispatches a regular action once the async function resolves, while in the first example, the action function itself is async, which redux doesn't like.

Cancel request if there is new one(promises)

I send request to server everytime user types something. I use debounce for the 400ms delay:
type = debounce((text) => {
this.props.actions.loadInfo(text)
}, 400);
When I type something, stop and start again and repeat it, several requests are send and I receive irrelevant data. I use promises:
export const loadInfo = (text) => dispatch => {
loadData(text).then(result => {
dispatch(showUserData(result));
});
};
export const loadData = async (text) => {
const tabData = await axios.get(`url&query=${text}`);
return tabData;
}
I need somehow cancel previous request if user sends the new one(when he typed something), what is the best way to do that? I expected debounce will help me but not. I use axios. This is not duplicate of questions here, I checked provided solutions but thet don't help me
The problem is similar to this one. Axios cancellation API can be used to cancel old requests. This should be done in a function that does a request (loadData) and has direct access to Axios, it may be also debounced:
let cancelObj;
export const loadData = debounce((text) => {
if (cancelObj) {
this.cancelObj.cancel();
}
cancelObj = CancelToken.source();
return axios.get(`url&query=${text}`, {
cancelToken: this._fetchDataCancellation.token
}).catch(err => {
// request wasn't cancelled
if (!axios.isCancel(err))
throw err;
});
}, 200);
Since Redux is used, other solutions may involve it, they depend on how Redux is used.
Even I tried to use debounce function in my code but the problem is that if user types very fast stop and then again start typing, in that case, your input values get updated and UI get distorted, to avoid this I used XMLHttpRequest and its abort() to cancel the previous calls, if calls do not succeed then it will be canceled,
you can try this solution, https://stackoverflow.com/a/55509957/9980970

Angular 2 Http polling not delivering errors

I am trying to poll a REST API to update a data table which is working fine with the following code:
pollData(url, interval) {
return Rx.Observable.interval(interval)
.mergeMap(() => this.http.get(url));
}
// get data
this.dataService.pollData(this.url, this.updateInterval)
.subscribe(
data => console.log(data),
err => console.log(err),
() => console.log('done'));
The problem is that error and complete never get called. Any suggestions to get this working with onError and onCompete would be greatly appreciated. Thanks!
About the onComplete call on the observer, it will be effected only when the source observable finishes. This means when the observable returned by pollData completes. As you are currently polling with no exit condition, then naturally your observable never completes.
To have this observable complete, you need to come up with an exit condition :
timeout (for instance, poll for X seconds, then stop polling)
number of polls
pollData-based condition (for instance, if no changes detected after X consecutive polling)
external completion signal
any other condition which makes sense to your use case
All these conditions are easy to implement with RxJS through they will require you to update the code of the pollData function.
For instance for the external completion signal, you could write :
// defining somewhere the subject for signalling end of polling
stopPollingS = new Rx.Subject();
// somehow pass this subject as a parameter of the polling function
pollData(url, interval, stopPollingS) {
return Rx.Observable
.interval(interval)
.mergeMap(() => this.http.get(url))
.takeUntil(stopPollingS);
}
// somewhere in your code when you want to stop polling
stopPollingS.onNext(true);
About the onError call on the observer, , I am not sure I get what is happening. Have you tried provoking an error and check the onError handler of your observer is indeed called? If there is no error, it is quite obvious that the onError will not be called.
Just in case anyone was wanting to know how I went about solving this problem and implemented the functionality that was required. Basically I just needed to wrap the observable in another and return the error as a data.
initiatePolling(url, interval) {
var http = this.http;
return Rx.Observable.create(function (observer) {
// initial request (no delay)
requestData();
var timerId = setInterval(requestData, interval);
function requestData() {
var subscription = http.get(url).timeout(20000)
.subscribe(
result => {
observer.next(result);
subscription.unsubscribe();
},
err => {
observer.next(err);
subscription.unsubscribe();
},
() => {
subscription.unsubscribe();
});
}
return function () {
observer.complete();
window.clearInterval(timerId);
}
});
}

Categories