Using Map in React with firebase - javascript

Someone please help. I have combed all of the doc's, examined all similar questions and really cannot see what I am missing.
I am new to react and trying to map over documents returned from an asynchronous call from a firebase db.
Here is the code:
class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
let posts = [];
db.collection("Posts").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
posts.push({
data: doc.data(),
id: doc.id
});
});
});
this.setState({
data: posts
});
}
renderPosts() {
console.log(this.state.data);
return this.state.data.map((post, index) => {
console.log(post);
console.log(index);
return (
<div>
{index}
</div>
)
})
}
render() {
return(
<div>
{this.renderPosts()}
</div>
);
}
}
I am sure it is something super simple but I am pulling my hair out. If I examine my state I can see the data. console.log(this.state.data); inside renderPosts even displays exactly what I need to map over, but everything inside the map callback doesn't return and I end up with a single empty Any help would be greatly appreciated.

As Li357 commented, the setState call needs to happen inside the get() callback:
db.collection("Posts").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
posts.push({
data: doc.data(),
id: doc.id
});
});
this.setState({
data: posts
});
});
The reason for this is that data is loaded from Firestore asynchronously, and by the time you were calling setState the posts variable was still empty.
The easiest way to understand this type of asynchronous loading is with a few log lines:
console.log("Before starting to get documents");
db.collection("Posts").get().then(function(querySnapshot) {
console.log("Got documents");
});
console.log("After starting to get documents");
When you run this, the output is:
Before starting to get documents
After starting to get documents
Got documents
That is probably not the order you expected the output in. But it is the expected behavior: instead of blocking the code while the data is loaded (which would freeze the browser), the code after the get().then() block immediately executes. And then when the data is loaded, the code in the then callback is executed. That's why all code that needs the documents from the database needs to be inside the then callback.

Related

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 Firebase read element

I'm trying to get the value stored in 'Nombre', but it doesn't work at all, am I doing something wrong?
class Appp extends React.Component {
constructor() {
super()
this.state = { name: 'hey' }
}
componentDidMount() {
const nameRef = firebase.database().ref().child('Anonimos').child('wx3czBh22dMQUTNDwD9l').child('Nombre')
nameRef.on('value', snapshot => {
this.setState({
name: snapshot.val()
})
})
}
render() {
return <h1>{this.state.name}</h1>
}
}
Your code uses the API for the Firebase Realtime Database. But the screenshot shows data in Cloud Firestore. While both databases are part of Firebase, they are completely separate and the API for one can't access the data in the other.
To access the data in Cloud Firestore, follow the documentation for that database: https://firebase.google.com/docs/firestore/quickstart
Something like:
componentDidMount() {
const nameRef = firebase.firestore().collection('Anonimos').doc('wx3czBh22dMQUTNDwD9l')
nameRef.onSnapshot(doc => {
this.setState({
name: doc.data().Nombre
})
})
}
Aside from the new syntax, the biggest change here is that this code reads the entire document, instead of just the Nombre field, and then sets just the name in the state. So while the end result is the same, this loads more data than your Realtime Database example would, since that API allow the loading of any node, while Cloud Firestore always loads complete documents.

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));
}

Multiple set state within multiple api call inside componentDidMount in ReactJS

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.

Double rendering in React with asynchronous call in componentDidMount causing error

I'm building a blog application that has an articles index page, and from there you can click on an article to see the article or edit the article.
If you're going from the index page to the edit page, it works just fine because I already have all the articles in state. But if I refresh after I've gone to the edit-article page, I no longer have all the articles in state.
This is a problem because I'm making an asynchronous recieveSingleArticle call in the componentDidMount of my edit-article page, then I setState so my form is prepopulated. There's a double render which causes an "Uncaught TypeError: Cannot read property 'title' of undefined" error, presumably during the first render before the article has been received into state.
class ArticleEdit extends React.Component {
constructor(props) {
super(props);
this.state = {title: "", body: "", imageFile: ""};
this.handleChange = this.handleChange.bind(this);
this.handlePublish = this.handlePublish.bind(this);
this.handleFile = this.handleFile.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
componentDidMount() {
const { article, requestSingleArticle } = this.props;
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
...
I tried wrapping my async calls inside of an "if (this.props.article)" but that didn't work. Is there a best way of dealing with this type of problem? Any advice greatly appreciated!
UPDATE:
Another solution that works is to have a componentDidUpdate in addition to componentDidMount. check in componentDidMount if this.props.article exists and if so, setState. And in componentDidUpdate, wrap the setState in the following conditional:
if (!prevProps.article && this.props.article)
Just check if the article is present in the props before calling async action
componentDidMount() {
const { article, requestSingleArticle } = this.props;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}
Since you are not getting any render from this method , it means that the props are not yet obtained in the life cycle method componnetDidMount. So instead you can use componentWillReceiveProps like this
componentWillReceiveProps(nextProp) {
// this line here will check the article props' status so that
// we will not use setState each time we get a prop
if (this.props.article === nextProp.article) return;
// rest is just the same code from above
const { article, requestSingleArticle } = nextProp;
if (!(article && requestSingleArticle)) return; // this line
requestSingleArticle(this.props.match.params.articleID)
.then(() => {
this.setState({
title: article.title,
body: article.body,
imageFile: article.imageFile
});
});
}

Categories

Resources