How do I deal with promises in setState? - javascript

I have the following react code, that represents a text input.
onChangeDestination(url, index) {
this.setState(prevState => {
const rules = [...prevState.rules];
rules[index] = { ...rules[index], url};
if (isURL(url+'')) {
testURL(url).then(r=> {
var url_status = r.data.response
rules[index] = { ...rules[index], url_status};
})
} else {
var url_status = "No URL"
rules[index] = { ...rules[index], url_status};
}
return { rules };
});
};
In English:
If the URL passes isURL() validation, then use custom function testURL() to see what the HTTP status of that URL is (using axios).
In my template, there's a {props.url_status} for the relevant bit.
The issue is, even though it's logging to the console the desired behaviour, it doesn't seem to be updating the viewport reliably, which I think is linked to the promise.
What am I doing wrong?

You could achieve it by converting your function to be asynchronous and calling your promise (if necessary) before your setState. This solution uses the easier to read async/await syntax and a ternary condition to choose the correct status value :
const url_status = isURL(url + '') ? (await testURL(url)).data.response : "No URL"
This line will execute your promise and wait for it only if isURL return true, if so it will return the response part and if not, it will send out "No URL".
Full code :
async onChangeDestination(url, index) {
const url_status = isURL(url + '') ? (await testURL(url)).data.response : "No URL"
this.setState(prevState => {
const rules = [...prevState.rules];
rules[index] = {
...rules[index],
url,
url_status
};
return { rules };
});
};

I recommend to use more components with some particular single task only. In this case: you might need a stateless Input component with an onChange and a value prop. Its parent could be some container where the onChange triggers some async request and has a state like url_status. As #Nit commented set the state in the promise then clause. This url_status will be a prop of some other component, so in case of the prop changes that component will re-render automatically... In most of the cases you do not need to use states at all.

Related

Hook in Mongoose works in PRE but doesn't work in POST

Using Mongoose hooks, I need that if the property called outstandingBalance has a value of zero, the status automatically changes to false.
Trying to do this using Mongoose's PRE hook works but only by re-invoking the request after outstandingBalance was already zero before. That is why I have decided to use the POST hook so that once the setting of outstandingBalance to zero is finished, it changes the property from statua to false.
This is the code that I use with PRE that works fine but is not really viable for what I need:
SaleSchema.pre('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery())
if (docToUpdate.outstandingBalance < 1) {
this._update.status = false;
}
})
So I decided to change PRE to POST but it never works:
SaleSchema.post('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery())
if (docToUpdate.outstandingBalance < 1) {
this._update.status = false;
}
})
'POST' means all done, there is no action after that(the data is already updated), you have to save it again after setting the status to update the status.
PRE hook is correct for your case, just change the condition: Checking on update data instead of current data
SaleSchema.pre('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery())
if (this._update.outstandingBalance < 1 || (!this._update.outstandingBalance && docToUpdate.outstandingBalance < 1)) {
this._update.status = false;
}
})
This was the solution to be able to set the status to false depending on the outstandingBalance value using the Pre hook:
SaleSchema.pre('findOneAndUpdate', function (next) {
if(this._update.$set.outstandingBalance < 1) {
this._update.status = false
}
next();
});
Many thanks to him for his help as #hoangdv guided me to find the solution.

Get value of current Observable state

So I am trying to get the value (the email) of my Observable<firebase.User>. I know there is something called BehaviourSubject, but I cannot use it since firebase.User requires an Observable, it seems.
retrieveUserData(){
let emailVal = "";
this.userData.subscribe(
{
next: x => emailVal += x.email && console.log(x.email),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
}
);
return emailVal;
}
So my goal is to get x.email in the emailVal let, in order to pass it & display, for example.
The problem is, that I am getting an (by logging the whole method retrieveUserData()), but the console.log(x.email) always returns the value I am looking for.
Why is that & is there a way to get the value & store it in a string, let or something else?
It is because Observables are async. It means when you run subscribe method of the observable, it runs the command without blocking the current runtime. Also, you are assigning the value of emailVal when the observable is run but you return the value without waiting for the assignment to be happened.
What you can do?
You can keep a global variable to keep the email globally and use that variable to display the email in the html side.
#Component({
selector: "my-app",
// See here, I used emailVal to display it
template: "<span>{{emailVal}}</span>",
styles: [""]
})
export class TestComponent {
emailVal = "";
ngOnInit(): void {
this.retrieveUserData();
}
retrieveUserData(): void {
this.userData.subscribe(
x => this.handleData(x.email),
err => console.error("Observer got an error: " + err)
);
}
handleData(email) {
// Here, we assign the value of global variable (defined in class level)
this.emailVal = email;
console.log(email);
}
}
You can use rxjs library in such a way to make the observable return the value and return the observable in the method as below
retrieveUserData(): Observable<firebase.User> {
return this.userData.pipe(
map(x => x.email)
);
}
And in html side, using async pipe (as an example):
<span>{{retrieveUserData() | async}}</span>
async pipe will subscribe to observable and wait for it to complete and then take the value and put it in the value of span. You can check this StackBlitz example to understand this method deeply.

React / ES6 - Efficiently update property of an object in an array

I am looking for the most efficient way to update a property of an object in an array using modern JavaScript. I am currently doing the following but it is way too slow so I'm looking for an approach that will speed things up. Also, to put this in context, this code is used in a Redux Saga in a react app and is called on every keystroke* a user makes when writing code in an editor.
*Ok not EVERY keystroke. I do have debounce and throttling implemented I just wanted to focus on the update but I appreciate everyone catching this :)
function* updateCode({ payload: { code, selectedFile } }) {
try {
const tempFiles = stateFiles.filter(file => file.id !== selectedFile.id);
const updatedFile = {
...selectedFile,
content: code,
};
const newFiles = [...tempFiles, updatedFile];
}
catch () {}
}
the above works but is too slow.
I have also tried using splice but I get Invariant Violation: A state mutation
const index = stateFiles.findIndex(file => file.id === selectedFile.id);
const newFiles = Array.from(stateFiles.splice(index, 1, { ...selectedFile, content: code }));
You can use Array.prototype.map in order to construct your new array:
const newFiles = stateFiles.map(file => {
if (file.id !== selectedFile.id) {
return file;
}
return {
...selectedFile,
content: code,
};
});
Also, please consider using debouncing in order not to run your code on every keystroke.

React Recoil state not being reset properly

I have some recoil state, that i want to reset.
import { useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil';
...
//should be used for flushing the global recoil state, whenever a user submits an invoice
const resetLabelInvoiceState = useResetRecoilState(labelInvoiceState);
const resetMetaDataState = useResetRecoilState(metadataState);
const resetGlobalAnnotationsState = useResetRecoilState(globalAnnotationState)
I have made function, that i suppoes to reset all the states like this. I have both tried with and without the reset function.
const flushRecoilState = () =>{
console.log('flushed state')
return(
resetLabelInvoiceState(),
resetMetaDataState(),
resetGlobalAnnotationsState()
)
}
...
flushRecoilState()
return history.push('/historyinvoices')
...
When i check the state it is not reset. Is it because the `useResetRecoilState´ is not working properly from the library, is not implemented properly, or is there some other problem.
I could just use the regular useRecoilState hook, and just set the state back to the default value.
Does anybody know why this could be?
I had the same problem today, it turns out to be my own fault. Just put it here for future reference.
My problem was that I changed the set method in the selector, if you customized the set method, you need to check if the incoming value is a DefaultValue.
const targetYear = selector({
key: 'targetYear',
get: ({get}) => get(targetYearAtom),
set: ({set, get}, method) => {
const currentTargetYear = get(targetYearAtom);
switch(method) {
case 'prevYear':
set(targetYearAtom, currentTargetYear - 1);
return;
case 'nextYear':
set(targetYearAtom, currentTargetYear + 1);
return;
default:
if (method instanceof DefaultValue) {
set(targetYearAtom, method);
}
return;
}
},
})

Variables Being Changed In Fetch But Unchanged Outside of Function

I have a React App and I have an App container where I want to call a fetch to get some data to pass through to components that will need that information.
I've declared variables outside of the an onload function (where my fetch is located) and assigned them different values in the fetch. They variables will change in the fetch but outside of the fetch they remain unchanged.
How come they are not staying changed and is there a way to fix this?
I've tried changed the variables declared with var instead of let and I've tried putting the function inside the const.
I've also tried putting the fetch inside of other components (like Table as seen below) but then I have to declare two states and call a fetch withint a fetch because I'm already calling another fetch there and it becomes cumbersome...
let latestRelease = 0;
let draftRelease = 0;
let doClone = false;
function onload() {
fetch(url, {
method: 'GET'
})
.then(function(response) {
return response.json();
})
.then(function(result) {
for(var i = 0; i < result.length; i++)
{
if(result[i].id > latestRelease && result[i].status === "released") {
latestRelease = result[i].id;
}
if(result[i].id > draftRelease && result[i].status === "draft") {
draftRelease = result[i].id;
}
}
if(latestRelease > draftRelease) {
doClone = true;
}
})
.catch((error) => {
console.log(error);
});
}
const App: React.FC = () => {
onload()
return (
<React.Fragment>
<CssBaseline />
<Container fixed>
<PersistentDrawerLeft/>
{console.log(latestRelease)} //displays 0
<Title/>
<Table />
</Container>
</React.Fragment>
);
}
export default App;
I'm expecting for latestRelease and draftRelease to not stay as 0 but anything greater than that but the output is just 0. With the correct values returned I'd then like to pass them as props to the components.
Many thanks!
Part of the issue is that you don't seem to be properly distinguishing between synchronous and asynchronous code. fetch is asynchronous, meaning that that code is not guaranteed to run before anything else in the file. (fetch uses JS promises to manage async data, so it helps to have a good grasp on using promises.)
In a typical React case, you want to do a few things differently. First, you want to use component state to hold on to the data, rather than just random variables (this allows React to re-render when those values change). Secondly, when you're fetching data asynchronously, you need to work out what your app should do before the fetch is complete.
Here's a very basic example showing how this could work:
import React, { useState, useEffect } from 'react'
const App = ({ url }) => {
// We'll use this variable to store an object with the details
const [releaseDetails, setReleaseDetails] = useState(null)
// When the component is loaded, we'll fetch the url (coming from the component props) and then
// run your logic.
useEffect(() => {
let latestRelease = 0;
let draftRelease = 0;
let doClone = false;
fetch(url)
.then((response) => response.json())
.then((result) => {
for(var i = 0; i < result.length; i++) {
if(result[i].id > latestRelease && result[i].status === "released") {
latestRelease = result[i].id;
}
if(result[i].id > draftRelease && result[i].status === "draft") {
draftRelease = result[i].id;
}
}
if(latestRelease > draftRelease) {
doClone = true;
}
// To make these details available to the component, we'll bundle them into an object
// and update the component's state:
setReleaseDetails({
latestRelease,
draftRelease,
doClone
})
})
.catch((error) => {
// You'd ideally want some proper error handling here
console.log(error)
});
}, []) // pass an empty array so this is only run when the component is first rendered
// Because we're getting the data asynchronously, we need to display something while we wait
if(releaseDetails === null) {
return "loading..."
}
// Once the data is available, you can then use the details when rendering. You could instead
// render a child component and pass the values as props to it.
return (
`LatestRelease: ${releaseDetails.latestRelease}`
)
}
Speaking generally, there are probably a few React and general JS concepts you'll want to make sure you have your around, particularly around state and async data fetching. Not sure how much experience you've had with it so far, but you may want to take a look at some intro tutorials (possibly like this official one) to see how much you can follow and if there's anything that jumps out as something you need to familiarise yourself with more.
can you please try with state variable ,because if state variable changes the render will call again, here your using a normal variable may be its changing but its not rendering.
Thank You.
Variable should be in the state for re rendering

Categories

Resources