2016-11-04 13 views
0

現在、リストに画像を追加(そして後でアップロード)するために、MeteorにカスタムReactコンポーネントを作成しています。しかし、リストからイメージを削除しようとすると、最後の要素は常にGUIから削除されます。最初は、これは間違ったインデックスを削除のために使用した単純なケースだと思っていましたが、それ以上のものが判明しました。 ImagesListコンポーネントがレンダリング中に使用する、(デバッグのために)いくつかの値で初期化することができレンダリングされたHTMLがReactコンポーネントと同期していません

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

これは私のImageListコンポーネントが現在のように見えるものです。例えば:

<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。

ImagesListの機能の中で、削除するインデックスをログに記録しておき、削除前後の値も記録します。記録されているインデックスは正しいです。この場合、インデックスは1です。削除の前に値は画像A、B、Cです。削除後、値は画像AとCです。

ImagesListrender()機能の中でいくつかのロギングを行うことにしました。残念ながら、これは正しい値AとCも記録しますが、実際にはAとBがレンダリングされます。

私はまた、forceUpdate()と一緒にローカル変数に格納するのではなく、このコンポーネントのReact状態を使用しようとしました。

もう1つは、React Developer ToolsプラグインをChrome用に使用することです。 Devtoolsも正しい値を表示しますが、GUIはまだ表示されません(this screenshot)。

私は現在、何を試してみるかというアイデアがありません。 私が提供したスニペットを使って、Meteorプロジェクトを作成し、このバグを再現できるはずです。

+1

一般に、それらの画像の原点にある画像に変更を加える必要があります。彼らは小道具としてあなたに与えられていたので、変更はコンポーネント自体で起こるべきではありません。これは通常アクションをトリガし、グローバル状態マネージャを使用して 'props'にマップされた状態を更新することによって行われます。私はあなたの質問でデモンストレーションする方法でコンポーネントのアップデートがどこで行われたのか、興味があります。 – MasterAM

答えて

2

MasterAMの提案で、私は2つの異なるソリューションを見つけることができました。


A.)componentWillUpdate()

this.value変数はImageItem成分のコンストラクタに一度だけ、すなわち設定されている使用します。変更が適切に委任されるようにするには、componentWillUpdate()機能内のthis.valueを更新する必要があります。以下のような何か:

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

B.)直接プロパティを使用する

これは間違いなく適切な解決策です。ここでは、ImageItemコンポーネントのコンストラクタ内でローカル変数this.valueを取り除きます。

render()の内部では、this.valuethis.props.imageに置き換えます。今すぐを使用せずに、関数を使用すると、すべて正常に動作します。

関連する問題