使用 Parse Server 返回的数据更新状态后,无法让 React 组件重新呈现

Can't get React component to re-render after state has been updated with returned data from Parse Server

我在使用状态数据渲染组件时遇到问题。我从 Parse Server 中提取数据,然后使用该数据更新状态。但是,我使用数据的组件(下面代码中的 <MenuItem />)在数据可用之前呈现。

我试过这里描述的方法:

但是我无法让组件识别正在更新然后重新呈现的状态。

有什么想法吗?代码完整如下。还附上了一张屏幕截图,显示了成功的控制台日志和数据的存在状态。

(请注意,我是 React 的新手,所以很多代码可能不是最优的。)

import React from "react";
import Parse from 'parse';

import MenuItem from './MenuItem';

class MenuItems extends React.Component {
  constructor() {
    super();

    this.loadMenuItems = this.loadMenuItems.bind(this);

    this.state = {
      menuItems: {},
      order: {}
    };
  }

  loadMenuItems() {
    /* 0a. Get copy of State? */
    const menuItems = {...this.state.menuItems};

    /* 0. Get User */
    // TODO: Get this from props
    var user = Parse.User.current();

    /* 1. Get Menu Items for this user */
    var MenuItemTest = Parse.Object.extend('MenuItemTest');
    var query = new Parse.Query(MenuItemTest);

    query.equalTo('user', user);
    query.find({
      success: function(returnedMenuItems) {
        console.log("Successfully retrieved " + returnedMenuItems.length + " menu items.");

        for (var i = 0; i < returnedMenuItems.length; i++) {
          var object = returnedMenuItems[i];

          const menuItem = {
            name: object.get('name'),
            price: object.get('price'),
            description: object.get('description'),
          }

          menuItems[`menu-item-${object.id}`] = menuItem;
        }
      },
      error: function(error) {
        console.log("Error: " + error.code + " " + error.message);
      }
    });

    this.setState({ menuItems });
  }

  componentWillReceiveProps(nextProps) {
    console.log(nextProps);
    this.loadMenuItems();
  }

  componentWillMount() {
    this.loadMenuItems();
  }

  render() {
    // Example from:
    // 

    if (!this.state.response) {
      return <div>Loading...</div>;
    }

    if (this.state.response.length === 0) {
      return <div>No menu items yet</div>;
    }

    return (
      <div className="menu-items">
        {
          Object
            .keys(this.state.menuItems)
            .map(key => <MenuItem key={key} details={this.state.menuItems[key]} />)
        }
      </div>
    )
  }
}

export default MenuItems;

更新代码以与评论线程一致

import React from "react";
import Parse from 'parse';

import MenuItem from './MenuItem';

class MenuItems extends React.Component {
  constructor() {
    super();

    this.loadMenuItems = this.loadMenuItems.bind(this);
  }

  loadMenuItems() {
    const menuItems = {};

    /* 0. Get User */
    var user = Parse.User.current();

    /* 1. Get Menu Items for this user */
    var MenuItemTest = Parse.Object.extend('MenuItemTest');
    var query = new Parse.Query(MenuItemTest);

    query.equalTo('user', user);
    query.find({
      success: function(returnedMenuItems) {
        console.log("Successfully retrieved " + returnedMenuItems.length + " menu items.");

        for (var i = 0; i < returnedMenuItems.length; i++) {
          var object = returnedMenuItems[i];

          const menuItem = {
            name: object.get('name'),
            price: object.get('price'),
            label: object.get('label'),
            shorthand: object.get('shorthand'),
            description: object.get('description')
          }

          menuItems[`menu-item-${object.id}`] = menuItem;
        }
      },
      error: function(error) {
        console.log("Error: " + error.code + " " + error.message);
      }
    });

    this.setState({ menuItems });
  }

  componentWillReceiveProps(nextProps) {
    console.log('I ran');
    console.log(nextProps);
    this.loadMenuItems();
  }

  componentWillMount() {
    this.loadMenuItems();
  }

  render() {
    // Handle case where the response is not here yet
    if (!this.state.menuItems) {
      console.log('No state');
      return <div>Loading...</div>;
    }

    // Gives you the opportunity to handle the case where the ajax request
    // completed but the result array is empty
    if (this.state.menuItems.length === 0) {
      console.log('Zero items');
      return <div>No menu items yet</div>;
    }

    if (this.state.menuItems) {
      return (
        <div className="menu-items">
          { console.log(this.state.menuItems) }
          { console.log(Object.keys(this.state.menuItems)) }
        </div>
      )
    }
  }
}

export default MenuItems;

就我从你这里得到的信息而言,

if (!this.state.response) {
  return <div>Loading...</div>;
}

if (this.state.response.length === 0) {
  return <div>No menu items yet</div>;
}

this.state.response 从未设置为状态,因此将始终显示 Loading...

我相信你想把它改成

if (!this.state.menuItems) {
  return <div>Loading...</div>;
}

if (this.state.menuItems.length === 0) {
  return <div>No menu items yet</div>;
}

并且您的 constructor 根本不设置 menuItems 状态(因此最初会显示加载)。

我找到了解决方案。虽然在尝试解决我的问题时,我移动了一些代码来设置父组件的状态并将其作为道具传递给 MenuItems。但是为什么它不起作用仍然是一样的。

loadMenuItems()有关:

loadMenuItems() {
  const menuItems = {};

  /* 0. Get User */
  var user = Parse.User.current();

  /* 1. Get Menu Items for this user */
  var MenuItemTest = Parse.Object.extend('MenuItemTest');
  var query = new Parse.Query(MenuItemTest);

  query.equalTo('user', user);
  query.find({
    success: function(returnedMenuItems) {
      console.log("Successfully retrieved " + returnedMenuItems.length + " menu items.");

      for (var i = 0; i < returnedMenuItems.length; i++) {
        var object = returnedMenuItems[i];

        const menuItem = {
          name: object.get('name'),
          price: object.get('price'),
          label: object.get('label'),
          shorthand: object.get('shorthand'),
          description: object.get('description')
        }

        menuItems[`menu-item-${object.id}`] = menuItem;
      }
    },
    error: function(error) {
      console.log("Error: " + error.code + " " + error.message);
    }
  });

  this.setState({ menuItems });
}

在执行 success 回调之前,this.setState({ menuItems }) 是 运行。所以状态总是空的,但是当我检查控制台中的所有内容时,它已经返回并循环遍历 menuItems[menu-item-${object.id}] = menuItem; 并且一切都在那里。

所以我所做的是在 success 回调中移动 this.setState({ menuItems }); 然后添加 .bind(this) 这样我就可以在那里设置状态:

success: function(returnedMenuItems) {
  ...
}.bind(this),

而且我相信这些组件永远不会更新,因为虽然状态是用 menuItems[menu-item-${object.id}] = menuItem; 循环设置的,但 this.setState() 从来没有再次调用,这就是触发组件刷新的原因。我可能对其中的某些内容不满意,但我认为这就是正在发生的事情。

我在下面发布了更新的代码以供将来参考。

父组件:

import React from "react";
import Parse from 'parse';

import MenuItems from './MenuItems';


class ManageMenu extends React.Component {
  constructor() {
    super();

    this.loadMenuItems = this.loadMenuItems.bind(this);
  }

  loadMenuItems() {
    const menuItems = {};

    /* 0. Get User */
    var user = Parse.User.current();

    /* 1. Get Menu Items for this user */
    var MenuItemTest = Parse.Object.extend('MenuItemTest');
    var query = new Parse.Query(MenuItemTest);

    query.equalTo('user', user);
    query.find({
      success: function(returnedMenuItems) {
        console.log("Successfully retrieved " + returnedMenuItems.length + " menu items.");

        for (var i = 0; i < returnedMenuItems.length; i++) {
          var object = returnedMenuItems[i];

          const menuItem = {
            name: object.get('name'),
            price: object.get('price'),
            label: object.get('label'),
            shorthand: object.get('shorthand'),
            description: object.get('description')
          }

          menuItems[`menu-item-${object.id}`] = menuItem;
        }

        this.setState({ menuItems });
      }.bind(this),
      error: function(error) {
        console.log("Error: " + error.code + " " + error.message);
      }
    });
  }

  componentWillMount() {
    this.loadMenuItems();
  }

  render() {
    // Handle case where the response is not here yet
    // console.log(this.state);
    if (!this.state) {
      console.log('No state');
      // Note that you can return false it you want nothing to be put in the dom
      // This is also your chance to render a spinner or something...
      return <div>Loading...</div>;
    }

    if (this.state.menuItems) {
      return (
        <div className="manage-menu">
          <div className="mt3 border-top">
            <h3 className="mt1 bold s2">Menu Items</h3>

            <div className="mt1">
              <MenuItems menuItems={this.state.menuItems} />
            </div>
          </div>
        </div>
      )
    }
  }
}

export default ManageMenu;

菜单项组件:

import React from "react";

import MenuItem from './MenuItem';

class MenuItems extends React.Component {
  render() {
    // Same as: const menuItems = this.props.menuItems;
    const { menuItems } = this.props;

    // No menu items yet
    if (menuItems.length === 0) {
      console.log('Zero items');
      return <div>No menu items yet</div>;
    }

    if (menuItems) {
      return (
        <div className="menu-items">
          {
            Object
              .keys(menuItems)
              .map(key => <MenuItem key={key} details={menuItems[key]} />)
          }
        </div>
      )
    }
  }
}

export default MenuItems;