Reputation: 153
In my react component, when the component mounts, I want to run the loadData() function to populate an array of people objects in the state.
This component is called UserDisplaySection.js
componentDidMount() {
this.loadData();
}
Which calls the function to load data:
loadData = () => {
const db = fire.database();
const ref = db.ref('User');
let currentState = this.state.people;
ref.on('value', (snapshot) => {
snapshot.forEach( (data) => {
const currentStudent = data.val();
let user ={
"email" : currentStudent.Email,
"name": currentStudent.Name,
"age": currentStudent.Age,
"location": currentStudent.Location,
"course": currentStudent.Course,
"interests": currentStudent.Interests
}
currentState.push(user);
});
});
this.setState({
people: currentState
});
}
From another component called Home.js, I render UserDisplaySection.js with the toggle of a button which sets the state of a boolean called willDisplayUser which is initialised as false by default.
handleDisplayUsers = e => {
e.preventDefault();
this.setState({
willDisplayUsers: !(this.state.willDisplayUsers),
});
}
render() {
return (
<div>
<button className="mainButtons" onClick={this.handleDisplayUsers}>View Users</button> <br />
{this.state.willDisplayUsers ? <UserDisplaySection/>: null}
</div>
);
}
Note: The function works and populates the state with the correct data, but only after I render the UserDisplaySection.js component for a second time and works perfectly until I reload the page.
Upvotes: 0
Views: 826
Reputation: 3863
The root of the problem is that you are setting state outside of the ref.on
callback.
Data fetch is async and the callback will be executed later in time. Since set state is outside the callback, it gets called called immediately after registering a listener, but before the data finished fetching. That's why it does not work on initial render/button click: it shows initial state, not waiting for the fetch to finish.
Also, using once
instead of on
might serve better for your case.
loadData = () => {
const db = fire.database();
const ref = db.ref('User');
let currentState = this.state.people;
ref.once('value', (snapshot) => {
// callback start
snapshot.forEach( (data) => {
const currentStudent = data.val();
let user ={
"email" : currentStudent.Email,
"name": currentStudent.Name,
"age": currentStudent.Age,
"location": currentStudent.Location,
"course": currentStudent.Course,
"interests": currentStudent.Interests
}
currentState.push(user);
});
this.setState({
people: currentState
});
// callback end
});
// setState used to be here, outside of the callback
}
Upvotes: 1
Reputation: 83093
By doing ref.on()
you are actually declaring a listener that "listens for data changes at a particular location", as explained in the doc. The "callback will be triggered for the initial data and again whenever the data changes". In other words it is disconnected from your button onClick
event.
If you want to fetch data on demand, i.e. when your button is clicked, you should use the once()
method instead, which "listens for exactly one event of the specified event type, and then stops listening".
Upvotes: 3