呈现的HTML与React组件不同步

时间:2016-11-04 11:58:24

标签: javascript reactjs meteor

我目前正在Meteor中创建一个自定义React组件,用于将图像添加到列表中(以后再上传它们)。但是,当我尝试从列表中删除图像时,始终从GUI中删除最后一个元素。最初我认为这只是一个使用错误索引进行删除的简单案例,但事实证明不仅如此。

这就是我的ImageList组件目前的样子:

import React from 'react';
import Dropzone from 'react-dropzone';
import cloneDeep from 'lodash.clonedeep';
import { ImageItem } from './image-item.js';

export class ImagesList extends React.Component {
  constructor(props) {
    super(props);

    this.values = this.props.images || [];

    this.onDrop = this.onDrop.bind(this);
    this.addImages = this.addImages.bind(this);
    this.deleteImage = this.deleteImage.bind(this);
    this.imageChanged = this.imageChanged.bind(this);
  }

  onDrop(files) {
    this.addImages(files);
  }

  onDropRejected() {
    alert('Invalid file type');
  }

  addImages(files) {
    files.forEach(file => {
      this.values.push({
        title: '',
        description: '',
        url: file.preview,
        file,
      });
    });

    this.forceUpdate();
  }

  deleteImage(index) {
    console.log('index to delete', index);
    console.log('images pre-delete', cloneDeep(this.values)); // deep-copy because logging is async
    this.values.splice(index, 1);
    console.log('images post-delete', cloneDeep(this.values)); // deep-copy because logging is async
    this.forceUpdate();
  }

  imageChanged(index, image) {
    this.values[index] = image;
    this.forceUpdate();
  }

  render() {
    console.log('--------RENDER--------');
    return (
      <div className="image-list">
        <div className="list-group">
          {this.values.length === 0 ?
            <div className="list-group-item">
              No images
            </div>
            :
            this.values.map((image, index) => {
              console.log('rendering image', image);
              return (
                <ImageItem
                  key={index}
                  image={image}
                  onDelete={() => { this.deleteImage(index); }}
                  onChange={(item) => { this.imageChanged(index, item); }}
                  deletable={true}
                />
              );
            })
          }
        </div>
        <Dropzone
          multiple={true}
          onDrop={this.onDrop}
          onDropRejected={this.onDropRejected}
          className="dropzone"
          activeClassName="dropzone-accept"
          rejectStyle={this.rejectStyle}
          accept={'image/*'}
        >
          <span>Drop files here</span>
        </Dropzone>
      </div>
    );
  }
}

可以使用在渲染期间使用的某些值(为了调试)初始化ImagesList组件。例如:

<ImagesList images={[
  { title: 'Image 1', description: 'Image 1 description', url: 'http://cssdeck.com/uploads/media/items/3/3yiC6Yq.jpg' },
  { title: 'Image 2', description: 'Image 2 description', url: 'http://cssdeck.com/uploads/media/items/4/40Ly3VB.jpg' },
  { title: 'Image 3', description: 'Image 3 description', url: 'http://cssdeck.com/uploads/media/items/0/00kih8g.jpg' },
]}/>

ImagesList为每个图片呈现ImageItem个组件。这就是这个组件的样子:

import React from 'react';
import { RIEInput, RIETextArea } from 'riek';

export class ImageItem extends React.Component {
  constructor(props) {
    super(props);

    this.placeholder = {
      title: 'Title',
      description: 'Description',
    };

    this.value = this.props.image;
  }

  render() {
    return (
      <div className="list-group-item">
        <div className="text-content">
          <h4>
            <RIEInput
              className="description"
              value={this.value.title.length <= 0 ?
              this.placeholder.title : this.value.title}
              change={(item) => {
                this.value.title = item.value;
                this.props.onChange(this.value);
              }}
              validate={(value) => value.length >= 1}
              classEditing="form-control"
              propName="value"
            />
          </h4>
          <span>
            <RIETextArea
              className="description"
              value={this.value.description.length <= 0 ?
              this.placeholder.description : this.value.description}
              change={(item) => {
                this.value.description = item.value;
                this.props.onChange(this.value);
              }}
              validate={(value) => value.length >= 1}
              classEditing="form-control"
              propName="value"
              rows="2"
            />
          </span>
        </div>

        <img className="thumb img-responsive"
          style={{width: '20%' }}
          src={this.value.url}
          alt="Image"
          data-action="zoom"
        />

        {this.props.deletable ?
          <div className="delete-btn">
            <span onClick={this.props.onDelete}>
              &times;
            </span>
          </div>
          :
        undefined }
      </div>
    );
  }
}

假设我有三张图片,图片A,B和C,我想删除图片B.按下删除按钮后,图片C 将从GUI中消失

deleteImage() ImagesList函数内,我正在记录要删除的索引,并记录删除前后的值。记录的索引是正确的,在这种情况下是索引1。在删除之前,值是图像A,B和C.删除后,值应为图像A和C,因为它们应该是。

我决定在render()的{​​{1}}函数中进行一些日志记录。不幸的是,这也会记录正确的值A和C,但实际上会呈现 A和B

我还尝试对此组件使用React状态,而不是将其与ImagesList一起存储在局部变量中。

我尝试过的另一件事是使用Chrome的React Developer Tools插件。 Devtools也显示正确的值,但GUI仍然没有,如this screenshot所示。

我目前不知道该尝试什么,任何帮助都将不胜感激! 使用我提供的代码段,您应该能够创建一个Meteor项目并重现此错误。

1 个答案:

答案 0 :(得分:2)

根据MasterAM的建议,我设法找到了两种不同的解决方案。

A。) 使用componentWillUpdate()

this.value变量仅在ImageItem组件的构造函数中设置一次。要确保正确委派更改,您必须更新this.value函数中的componentWillUpdate()。类似的东西:

componentWillUpdate(nextProps, nextState) {
  this.value = nextProps.image;
}

B。) 直接使用该属性

这绝对是更合适的解决方案。在这里,我们摆脱了this.value组件的构造函数中的局部变量ImageItem

render()功能中,您将this.value替换为this.props.image。现在没有必须使用componentWillUpdate()功能,一切都按预期工作。