【问题标题】:React + Firebase Component does not re-render on data change (remove)React + Firebase 组件不会在数据更改时重新渲染(删除)
【发布时间】:2016-03-05 22:16:54
【问题描述】:

这是一个渲染/性能问题。

我已经使用 Firebase 构建了一个基本的 React 应用程序。它基本上可以工作,但有一个明显的异常行为:渲染并非完全独立发生。

具体来说,我检索用户所属的组列表,并希望能够添加/删除组,从而触发重新渲染。我正在使用 Firebase 的 .on('value')、.on('child_added') 和 .on('child_removed') 函数来绑定对这些事件的引用。

添加和删除组有效,但是组列表消失了(特别是在删除时注意到),我必须单击一个按钮(字面意思是 UI 中的任何按钮)才能将其恢复(它会立即弹出,并带有所有基于用户操作的正确数据)。

所以 - 我显然在某个地方遗漏了一些东西,尽管我一直在测试各种修复程序并且还没有能够理解这个问题。组件是:

主管理组件:

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;

然后是 GroupList 组件:

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;

非常感谢任何帮助!

【问题讨论】:

    标签: javascript reactjs firebase


    【解决方案1】:

    你似乎已经明白你需要什么了。在您的componentWillMount 方法中,您可以注册各种userRef 事件。您只需要注册groupRef 事件,使用相同的回调。每当调用组件 setState 方法时,React 都会重新渲染,您正在 buildGroupList 中执行此操作。您只需在groupRef 更新后致电buildGroupList

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

    【讨论】:

    • 我确实喜欢更简洁的代码(非常感谢 - thx),但不幸的是,除了单击页面上的按钮/链接之外,没有什么会触发重新渲染。组列表实际上是根据用户配置文件数据中列出的组构建的,这就是 ref.on() 正在“监视”的内容。当我删除一个组时,整个列表消失并且该组在 Firebase 数据库中被删除,但在有其他用户操作(单击按钮/链接)之前不会重绘该表。但是随后列表弹出,并且是正确的。奇怪...
    • 我很难遵循所有这些。你有没有机会用 CodePen 重现这个?
    • 我会看看我是否可以将它放到带有虚拟数据和禁用登录的生产环境中。完成后我会发布链接 - 非常感谢您的帮助。
    【解决方案2】:

    我相信这是因为使用了.once()。根据 Firebase 的文档,.once() 用于查询数据库位置,而无需附加持久的侦听器,例如.on('value', callBack)。但是,当我将代码中调用.once() 的实例更改为使用.on() 时(即使我不想在那里附加一个侦听器,而是简单地检索一次数据),所有不稳定的行为都停止了,并且我的应用程序更新了this.state 和我最初期望的组件。

    我唯一能从这次经历中得出的结论是 .once() 没有按预期/声明的方式发挥作用,应该改用 .on()(加上适当的 .off() 引用)。

    【讨论】:

    • 我也有同样的问题,但没有任何意义。如果数据通过ononce 到达,则无论如何都应该重新渲染。