Reputation: 415
This is a rendering / performance problem.
I've built a basic React App using Firebase. It basically works, with one notable anomalous behavior: rendering doesn't occur entirely on its own.
Specifically, I retrieve a list of groups to which a user belongs, and want to be able to add/remove groups, triggering a re-render. I'm using Firebase's .on('value'), .on('child_added'), and .on('child_removed') functions to bind references to these events.
Adding and removing groups works, however the group list disappears (especially noticed on remove), and I have to to click on a button (literally, any button in the UI) to bring it back (it pops up immediately, with all the correct data based on user action).
So - I'm clearly missing something somewhere, although I've been testing various fixes and have yet to be able to understand the issue. Components are:
Main Admin Component:
var React = require('react'),
groupRef = new Firebase('http://my-url/groups'), //list of groups
userRef = new Firebase('http://my-url/users'), //list of users
groupListArray = [];
var AdminContainer = React.createClass({
mixins: [ReactFireMixin],
getInitialState: function() {
return {
groups: []
}
},
buildGroupList: function (dataSnapshot) {
groupListArray = [];
var groupList = dataSnapshot.val();
/* The group ids are saved as children in the db
* under users/$userid/groups to reflect which groups a user can
* access - here I get the group ids and then iterate over the
* actual groups to get their names and other associated data under
* groups/<misc info>
*/
for (var key in groupList) {
if (groupList.hasOwnProperty(key)) {
groupRef.child(key).once('value', function(snapShot2) {
groupListArray.push(snapShot2);
});
}
}
this.setState({
groups: groupListArray
});
},
componentWillMount: function() {
userRef.child(auth.uid).child('groups').on('value', this.buildGroupList);
userRef.child(auth.uid).child('groups').on('child_removed', this.buildGroupList);
userRef.child(auth.uid).child('groups').on('child_added', this.buildGroupList);
},
handleRemoveGroup: function(groupKey){
userRef.child(auth.uid).child('groups').child(groupKey).remove();
},
render: function() {
<div id="groupAdminDiv">
<table id="groupAdminTable">
<thead>
<tr>
<th>Group Name</th>
<th>Action</th>
</tr>
</thead>
<GroupList groups={this.state.groups} remove={this.handleRemoveGroup} />
</table>
</div>
}
});
module.exports = AdminContainer;
Then the GroupList Component:
var React = require('react');
var GroupList = React.createClass({
render: function() {
var listGroups = this.props.groups.map((group, index) => {
if (group != null) {
return (
<tr key={index} className="u-full-width">
<td>
{group.val().group_name}
</td>
<td>
<button className="button" onClick={this.props.remove.bind(null, group.key())}>Remove</button>
</td>
</tr>
)
} else {
return (
<tr>
<td>You have not added any Groups.</td>
</tr>
)
}
});
return (
<tbody>
{listGroups}
</tbody>
)
}
});
module.exports = GroupList;
Any help much appreciated!!
Upvotes: 1
Views: 2970
Reputation: 415
I believe this was caused by use of .once()
. As per Firebase's documentation, .once()
is used to query a database location without attaching an enduring listener such as .on('value', callBack)
. However, when I changed the instances in my code where I was calling .once()
to use .on()
(even though I didn't want to attach a listener there but instead simply retrieve data one time), all the erratic behavior stopped and my app updated this.state
and components as I had originally expected it to.
The only thing I can take away from this experience is that .once()
does not function as intended / stated and that .on()
(plus appropriate .off()
references) should be used instead.
Upvotes: 1
Reputation: 19847
You appear to already understand what you need. In your componentWillMount
method you register for various userRef
events. You just need to register for the groupRef
events, using the same callback. React re-renders whenever a components setState
method is called, which you are doing inside buildGroupList
. You just need to call buildGroupList
after the groupRef
updates.
componentWillMount: function() {
var events = ['value', 'child_removed', 'child_added'];
var refs = [groupRef, userRef.child(auth.uid).child('groups')];
var callback = this.buildGroupList;
refs.forEach(function(ref) {
events.forEach(function(e) {
ref.on(e, callback);
});
});
},
Upvotes: 2