setState 更新状态并触发渲染,但我仍然看不到它

setState updates state and triggers render but I still don't see it in view

我在 React 中有一个简单的 word/definition 应用程序。当用户单击“编辑”时,会弹出一个编辑框来更改定义。当我调用 getGlossary() 时,所提供的新定义在状态中更新,我在检查器中看到了新定义,并且我的 App render() 函数中的 console.log 语句也触发了。不幸的是,我仍然需要刷新页面才能在屏幕上看到新的定义。我认为在应用程序中调用 this.state.glossary 的设置状态会触发重新渲染到 GlossaryList,然后再渲染到 GlossaryItem 以更新它的定义,但我没有看到它:(.

App.js

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      glossary: [],
      searchTerm: '',
    }

    this.getGlossary = this.getGlossary.bind(this); //not really necessary?
    this.handleSearchChange = this.handleSearchChange.bind(this);
    this.handleAddGlossaryItem = this.handleAddGlossaryItem.bind(this);
    this.handleDeleteGlossaryItem = this.handleDeleteGlossaryItem.bind(this);
    //this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
  }

  getGlossary = () => {
    console.log('getGlossary fired');
    axios.get('/words').then((response) => {
      const glossary = response.data;
      console.log('1: ' + JSON.stringify(this.state.glossary));
      this.setState({ glossary }, () => {
        console.log('2: ' + JSON.stringify(this.state.glossary));
      });

    })
  }

  componentDidMount = () => {
    //console.log('mounted')
    this.getGlossary();
  }

  handleSearchChange = (searchTerm) => {
    this.setState({ searchTerm });
  }

  handleAddGlossaryItem = (glossaryItemToAdd) => {
    //console.log(glossaryItemToAdd);
    axios.post('/words', glossaryItemToAdd).then(() => {
      this.getGlossary();
    });
  }

  handleDeleteGlossaryItem = (glossaryItemId) => {
    console.log('id to delete: ' + glossaryItemId);
    axios.delete('/words', {
      data: { glossaryItemId },
    }).then(() => {
      this.getGlossary();
    });
  }

  render() {
    console.log('render app fired');
    const filteredGlossary = this.state.glossary.filter((glossaryItem) => {
      return glossaryItem.word.toLowerCase().includes(this.state.searchTerm.toLowerCase());
    });

    return (
      <div>
        <div className="main-grid-layout">
          <div className="form-left">
            <SearchBox handleSearchChange={this.handleSearchChange} />
            <AddWord handleAddGlossaryItem={this.handleAddGlossaryItem} />
          </div>
          <GlossaryList
            glossary={filteredGlossary}
            handleDeleteGlossaryItem={this.handleDeleteGlossaryItem}
            getGlossary={this.getGlossary}
          //handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
          />
        </div>
      </div>
    );
  }
}

export default App;

GlossaryItem.jsx

import React from 'react';
import EditWord from './EditWord.jsx';
const axios = require('axios');

class GlossaryItem extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isInEditMode: false,
        }
        this.glossaryItem = this.props.glossaryItem;
        this.handleDeleteGlossaryItem = this.props.handleDeleteGlossaryItem;
        this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
        this.handleEditClick = this.handleEditClick.bind(this);
    }

    handleUpdateGlossaryDefinition = (updateObj) => {
        console.log('update object: ' + JSON.stringify(updateObj));
        axios.put('/words', {
            data: updateObj,
        }).then(() => {
            this.props.getGlossary();
        }).then(() => {
            this.setState({ isInEditMode: !this.state.isInEditMode });
            //window.location.reload();
        });
    }

    handleEditClick = () => {
        // display edit fields
        this.setState({ isInEditMode: !this.state.isInEditMode });
        // pass const name = new type(arguments); data up to App to handle with db
    }

    render() {
        return (
            <div className="glossary-wrapper">
                <div className="glossary-item">
                    <p>{this.glossaryItem.word}</p>
                    <p>{this.glossaryItem.definition}</p>
                    <a onClick={this.handleEditClick}>{!this.state.isInEditMode ? 'edit' : 'cancel'}</a>
                    <a onClick={() => this.handleDeleteGlossaryItem(this.glossaryItem._id)}>delete</a>
                </div>
                {this.state.isInEditMode ?
                    <EditWord
                        id={this.glossaryItem._id}
                        handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
                    /> : null}
            </div>
        );
    }
}

EditWord

import React from 'react';

class EditWord extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            definition: ''
        };

        this.handleUpdateGlossaryDefinition = this.props.handleUpdateGlossaryDefinition;
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(event) {
        let definition = event.target.value;
        this.setState({ definition });
    }

    handleSubmit(event) {
        //console.log(event.target[0].value);
        let definition = event.target[0].value;
        let update = {
            'id': this.props.id,
            'definition': definition,
        }
        //console.log(update);
        this.handleUpdateGlossaryDefinition(update);
        event.preventDefault();
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit} className="glossary-item">
                <div></div>
                <input type="text" name="definition" placeholder='New definition' value={this.state.definition} onChange={this.handleChange} />
                <input type="submit" name="update" value="Update" />
            </form>
        );
    }
}
export default EditWord;

谢谢

我认为解决此问题的一种可能方法是映射数据以使 id 唯一标识每个列表项(即使在更新的情况下)。我们可以在 getGlossary() 中通过将 _id 修改为 _id + definition 来做到这一点。

getGlossary = () => {
  console.log('getGlossary fired');
  axios.get('/words').then((response) => {
    // Map glossary to uniquely identify each list item
    const glossary = response.data.map(d => {
      return {
        ...d,
        _id: d._id + d.definition,
      }
    });
 
    console.log('1: ' + JSON.stringify(this.state.glossary));
    this.setState({ glossary }, () => {
      console.log('2: ' + JSON.stringify(this.state.glossary));
    });
  })
}

在我设置的GlossaryItem的构造函数中

this.glossaryItem = this.props.glossaryItem;

因为我懒,不想在组件里写'props'两个字。事实证明,这以某种方式做出了松散引用的反应。

如果我只是删除这行代码并将所有对 this.glossaryItem.xxx 的引用更改为 this.pros.glossaryItem.xxx 那么它会按我预期的那样工作!另一方面,代码行可以移到渲染函数(而不是构造函数)中,这也可以,但必须确保我在渲染之外的其他函数中正确访问变量。