Multiple set state within multiple api call inside componentDidMount in ReactJS - javascript

I am new in React and trying to call multiple api call within componentDidMount function.
My code is
componentDidMount() {
Promise.all([
axios.get(<url1>),
axios.get(<url2>)
]).then(([res1, res2]) => {
// call setState here
const users = res1.data.users;
this.setState({ users: users});
const banks = res2.data.banks;
this.setState({ banks: banks});
console.log("Users")
console.log(users) // It works
console.log("Banks")
console.log(banks) // It works
})
}
render() {
console.log(this.state.users.length) // Gives length
console.log(this.state.banks.length) // Gives undefined
return (
<div className='popup'></div>
)
}
The problem is inside render function the second state banks length is undefined.
How can I do multiple setstate inside componentDidMount.
Any help is highly appreciated.
Update: Resolved
The mistake was
constructor(props) {
super(props);
this.state = {
users: [],
//MISSING BANKS array
}
}

You should set state in a single update, updating both values at the same time. Otherwise you are instructing React to initiate two renders, the first would contain users value and an undefined value for banks (depending on your initial state declaration). This render would be quickly followed by a second pass, in which both state values users and banks would be defined.
The below example should work as required, in a single render.
Promise.all([
axios.get(<url1>),
axios.get(<url2>)
]).then(([res1, res2]) => {
// call setState here
const users = res1.data.users;
const banks = res2.data.banks;
this.setState({ users, banks });
});
On the other hand, if for some strange requirement you actually want two sequential renders you can use setState's done callback; example below.
this.setState({ users }, () => {
this.setState({ banks });
});
This will ensure the first render is complete before requesting a new render via setState.

Related

React JS Updating item in State object to take effect immediately

React JS class component
I know there have been many posts on this subject but I can't seem to get this scenario to work.
Basically on my HandleClickSave event I want to update an item in my object in state without affecting the other values and then passing this updated oblect onto my service to get updated in the db.
The 'item' in question is the 'design' from the (unLayer) React-email-editor.
Problem is after the service is run in 'HandleClickSave' point 3 below, the receiving field 'DesignStructure' in the db is NULL every time. The other two fields are fine as these are saved to state object elsewhere.
Part of the problem is that the Email-Editor doesn't have an 'onChange' property which is where I would normally update the state. The other two values in the object are input texts and they do have an onChange which is how their state counterparts are updated.
This is the object 'NewsletterDesign':
{
"DesignId": "1",
"DesignName": "DesignLayout 1 Test",
"DesignStructure": null
}
In my React class component...
this.state = {
NewsletterDesign: {}
}
And the HandleClickSave event....
HandleClickSave () {
const { NewsletterDesign } = this.state
this.editor.saveDesign((design) => {
this.setState(prevState => ({
NewsletterDesign: {
...prevState.NewsletterDesign,
DesignStructure: design
}
}));
// Update to database passing in the object 'NewsletterDesign'. Field 'DesignStructure' in db is null every time, but other two fields are updated.
NewsletterService.UpdateCreateNewsletterDesign(NewsletterDesign)
etc....etc..
React's setState is not update immediately. read more here.
You can simply do it inside setState by
this.setState(prevState => {
const newState = {
NewsletterDesign: {
...prevState.NewsletterDesign,
DesignStructure: design
}
};
NewsletterService.UpdateCreateNewsletterDesign(newState.NewsletterDesign);
return newState;
});
The setState is an async operation. Meaning, that it's not guaranteed that the new state that you have updated will be accessible just after the state is updated. You can read more here
So in such cases, one of the way is to do the required operation first and then use the result at multiple places.
HandleClickSave () {
const { NewsletterDesign } = this.state
this.editor.saveDesign((design) => {
let newNewsletterDesign = { ...NewsletterDesign,
DesignStructure: design
};
this.setState(newNewsletterDesign);
NewsletterService.UpdateCreateNewsletterDesign(newNewsletterDesign)

Show/hide button based on state React

I have a button called download, it is a component. The functionality is written in a container.
I want the button to be hidden as default, unless the "(i === number)" condition is true in the download function.
However, it is tricky, since this condition will only be validated when I click "download". I am not sure how do I validate this logic beforehand to determine if the button needs to be displayed or not.
Can you please help? I am trying to set a state for showing button, but it doesn't work.
My code basically looks like this -
container:
state = {
showButton: false,
};
componentDidMount() {
this.download();
};
download = async () => {
const data = await this.props.client
.query({
query: numberQuery,
fetchPolicy: "no-cache",
})
// retrive data from number query ....
const contentData = data.data.content;
// retrieve and format numbers
const numbers = this.getNumbers(contentData);
// call get number and get the individual number here
const number = await this.getNumber();
numbers.forEach((i) => {
// this is to check if numbers contain the number from getNumber(), if
// number matches number in numbers
if (i === number) {
this.setState({
showButton: true,
});
// call functions, start downloading
};
});
};
render() {
return (
{this.state.showButton ?
<Download
onStartDownload={() => this.download()}
/> : null}
);
};
component:
class Download extends Component {
state = {
startDownload: false,
};
startDownload = () => {
this.props.onStartDownload();
};
render() {
return (
<Fragment>
<Button
id="download"
onClick={this.startDownload}
>
Download
</Button>
</Fragment>
);
};
};
If I understand correctly, your problem is that the numbers are fetched (and, in turn, the logic for showing/hiding the button is run) only after the download button was clicked, but you want it to run as soon as possible (that is, on the component's mount).
Generally, the fetching of data in React is separated from the render/event handler logic. In your case, a solution is to fetch the data when the component mounts, save it in state (such as a numbers and number fields), then, when rendering, check if number is in the numbers array.
For example:
// Container component; child component remains the same
state = {
number: null,
numbers: []
};
componentDidMount() {
this.fetchNumbers();
this.fetchNumber();
};
async fetchNumbers() {
const data = await this.props.client
.query({
query: numberQuery,
fetchPolicy: 'no-cache',
});
// retrive data from number query ....
const contentData = data.data.content;
// retrieve and format numbers
const numbers = this.getNumbers(contentData);
this.setState({ numbers });
}
async fetchNumber() {
// Assuming this is another HTTP request or something similar
const number = await this.getNumber();
this.setState({ number });
}
download = async () => {
// *only* downloading logic
};
render() {
const { number, numbers } = this.state;
const showDownload = numbers.includes(number);
return showDownload
? <Download onStartDownload={() => this.download()}/>
: null;
};
}
Notes:
Using Array.includes() instead of an Array.forEach() loop simplifies the code (and will probably save you some bugs in the future!)
I separated fetchNumbers(), which fetches this.state.numbers, from fetchNumber(), which fetches this.state.number, simply because they seem like two separate pieces of state (and therefore fetching them independently is most efficient); you could improve it even further by having the two functions return the data (instead of changing the state), then using Promise.all() in componentDidMount() (I left it out for simplicity's sake).
Generally in web dev, a best practice when fetching core data asynchronously is to indicate that the component is not loaded until the data arrives (using a loader, for example). For practice or prototypes it might not be your top priority, but keep that in mind for production.
you need a constructor in your class component and to update the state you need to use setState(),
review the code to follow
:
class Download extends react.Component {
constructor() {
super();// required
this.state = {
startDownload: false
};
}
startDownload = () => {
this.setState((prevState) => ({
// I use prevState, to bring the previous state value
startDownload: !prevState.startDownload // here the value is inverted
}));
};
render() {
return (
<> // this is equivalent to the fragment
<button id="download" onClick={this.startDownload}>
Download
</button>
</>
);
}
}

react component not running the cleanup function on unmount. Working with firestore

I have a react component useEffect hook that looks like the following. I am working with firestore. I am trying to remove a value from an array in firestore when the component unmounts. However, the value is not getting removed. I tried running the firestore query in the cleanup function independently to see if that query was the problem, but it's working fine independently. It's just not getting executed when it's inside the cleanup function. I THINK the problem is that my cleanup function at the end of the useEffect hook is not getting called when the component unmounts(for example when I close the window). does anyone know what I may be doing wrong? Thank you for your help in advance
useEffect(() => {
.......
return () => {
fire.firestore().collection("ActiveUsers").doc(utilvar.teacherID).get().then((snapshot) => {
var docref = snapshot.ref;
return docref.update({
active_users : fieldValue.arrayRemove({id: currentUser.uid, name: displayName})
})
})
};
}, []);
From my observation. utilvar.teacherID might not be ready as at the time your component got mounted.
So you may want to add it to the dependable array.
Your useEffect should therefore look something like this :
useEffect(() => {
.......
return () => {
fire.firestore().collection("ActiveUsers").doc(utilvar.teacherID).get().then((snapshot) => {
if(snapshot.exist){
return snapshot.ref.update({
active_users : fieldValue.arrayRemove({id: currentUser.uid, name: displayName})
}).catch(err=>err);
})
};
}, [utilvar.teacherID]);
I added the catch as it a promise being returned. It must be handled appropriately irrespective of where it's being used.

React Class passing stale props to Child stateless component

My Parent Component represents a form.
The users filling in the form have access to information in the form that is updated in real time as they update certain fields.
The Issue I am running into is. On one of these updates when we fetch the new data and pass it to the child randomly sometimes the child is receiving stale props. From the previous request.
The structure is something like this.
export class Form extends React.Component<Props, State> {
fetchUpdates = async (payload) => {
this.setState({ isLoadingUpdates: true })
await Service.getUpdates(payload)
.then(response => {
this.setState({ isLoadingUpdates: false, updates: response.data })
})
.catch(({ data: errors }) => this.setState({ isLoadingUpdates: false }))
}
}
render () {
const {
updates,
isLoadingUpdates,
} = this.state
<FormCombobox
onChange={this.fetchUpdates}
md={10}
name="field"
id="field"
label="Field"
onMenuOpen={() => forceCheck()}
openMenuOnClick
selectRef={this.itemSelect}
value={values.item}
options={itemOptions || []}
/>
<Info
data={updates}
errorMessage={this.state.updatesError}
/>
}
}
It doesn't occur every time but randomly either when the form is first updated or on one of the following updates the < Info > container recieves the previous requests response data. How can I stop the parent from passing stale data?
The problem here is that when fetchUpdates is called multiple times it gets out of order due to network delay. Let's say fetchUpdates is called three times, and let's say the request takes 5, 2 and 4 seconds respectively to complete. In this case, you can see that the second request calls setState before the first request. As a result, the info component gets passed the first value after the second value. This is the reason why it is intermittent.
Using await here won't help, because the fetchUpdates function calls are independent of each other.
One more thing, I noticed that you have isLoadingUpdates. But it's not being used anywhere in the code. And also doing,
if (!this.state. isLoadingUpdates) {
await Service.getUpdates(payload)
.then(response => {
this.setState({ isLoadingUpdates: false, updates: response.data })
})
.catch(({ data: errors }) => this.setState({ isLoadingUpdates: false }))
}
won't work because then it means you will miss keypresses when the network call is ongoing.
I would suggest using a debounce for the inputs. You can find how to do debounce here: Perform debounce in React.js

React- this shows populated state but this.state is empty

I have a component that renders the user's friends, and I need to get information about them. I have the function below called in componentDidMount that gets information about the friends and puts the data in state:
getFriends = ids =>{
const config = {
headers: {
token: localStorage.getItem('token')
}
};
axios.post('http://localhost:8082/api/friend/getAll', {friends: ids}, config)
.then(res=>this.setState({friends: res.data.friends}))
.catch(err=>console.log(err));
console.log(this);
console.log(this.state)
}
The problem is this shows the correctly populated state:
But this.state shows an "empty" state:
I am confused as to why those 2 are different. It shouldn't be a binding issue because I'm using arrow functions. Any help would be appreciated!
State updates are asynchronous and also the console values are resolved when you expand the object
So for instance when you log this and later when you try to expand the printed object, the state is evaluated and it shows you the updated state since by that time the state got updated.
However when you log this.state the updated value is not shown since the object is already printed on your screen
Please read
Value was evaluated just now with console.log on JavaScript object
setState doesn't update the state immediately
You can see the updated value of state if you log it in the callback function of setState
getFriends = ids =>{
const config = {
headers: {
token: localStorage.getItem('token')
}
};
axios.post('http://localhost:8082/api/friend/getAll', {friends: ids}, config)
.then(res=>this.setState({friends: res.data.friends}, () => {
console.log(this.state);
}))
.catch(err=>console.log(err));
}

Categories

Resources