2016-11-04 57 views
0

我目前在Meteor中創建了一個自定義的React組件,用於將圖像添加到列表(以及稍後上傳它們)。但是,當我嘗試從列表中刪除圖像時,最後一個元素總是從GUI中刪除。起初,我認爲這只是使用錯誤索引進行刪除的一個簡單情況,但事實證明這不僅僅是一個例子。渲染的HTML與React組件不同步

這是我的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。

ImagesListdeleteImage()函數中,我記錄了要刪除的索引,並記錄了刪除之前和之後的值。記錄的索引是正確的,在這種情況下,索引爲1。在刪除之前,值是圖像A,B和C.在刪除之後,值是圖像A和C,它們應該是。

我決定在ImagesListrender()函數中做一些日誌記錄。不幸的是,這也會記錄正確的值A和C,但實際上會渲染A和B

我也嘗試將React狀態用於此組件,而不是將它與forceUpdate()一起存儲在局部變量中。

我試過的另一件事是使用Chrome的React開發工具插件。 Devtools也顯示正確的值,但GUI仍然沒有,如this screenshot所示。

我目前的想法是什麼嘗試,任何幫助將不勝感激! 使用我提供的片段,您應該能夠創建一個Meteor項目並重現此錯誤。

+1

通常,您應該觸發對這些圖像原點上的圖像的更改。既然它們是作爲道具給予你的,那麼變化不應該發生在組件本身中。這通常是通過觸發一個動作來完成的,然後使用一個全局狀態管理器來更新映射到'props'的狀態。我很好奇你在哪裏看到組件更新以你在問題中演示的方式完成。 – MasterAM

回答

2

隨着MasterAM的建議,我設法找到了兩種不同的解決方案。


A.)使用componentWillUpdate()

this.value變量在ImageItem部件的構造設置一次即。爲確保更改得到正確委派,您必須更新componentWillUpdate()函數中的this.value。喜歡的東西:

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

B.)使用該屬性直接

這絕對是更合適的解決方案。在這裏,我們擺脫ImageItem組件的構造函數內部的局部變量this.value

render()函數內部,您可以使用this.value替換爲this.props.image。現在沒有必須使用componentWillUpdate()功能,一切都按預期工作。