2017-02-05 13 views
0

注:これはかなり長い説明です。再現可能なJSBinやそれに類する実装のために必要とされるコンポーネントの数が増えるにつれ、記述がより安全で実行可能な選択であるように思えました。ReactJS/Router/Flux:警告:マウントされたコンポーネントまたはマウントされているコンポーネントのみをアップデートできます

TL; DR私Flux(3.1.2)/React(15.4.2)/React-Router(3.0.2)アプリで

は、私はいつも私がルート間を移動ONLYすると、次のエラー/警告を取得するように見える:

warning.js:36 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the AllVideos component.

私の努力

私はちょっと調べましたが、情報の大部分は、聴かれたイベントが削除されないケースを指しているようです。私はすべてのバリエーションを試しましたが、うまくいかないようです。

いくつかの背景情報

私の努力は、私がReactJSにかなり新しいですので、ハンズオン学習/実践経験を確保するために、主に開発された非常にシンプルなアプリです。バックエンドでの

Node/ExpressサーバーはReact-routerFluxに助けられ、私のReactJSアプリで消費されるデータ(RESTエンドポイントのようなもの)があります。

最初のデータはSQLite DBに保存されており、リクエストされるたびにJSONと返されます。たとえば、アプリケーションの読み込み時には、私のサーバーにAJAX呼び出し(Axios経由)を開始してJSONで結果のサブセットを返し、その後私のFluxストアに保存します。ステートフルコンポーネントは、このデータを使用して必要に応じてビューを操作します。

マイRoutes.jsは、この構造を有する:

const Routes = (props) => ( 
    <Router {...props}> 
    <Route component={MainContainer}> 
    <Route path="/" component={App}> 
    <IndexRoute component={Home} /> 
    <Route path="videos" component={Videos}></Route> 
    <Route path="videos/:mediaid" component={MediaDetails} /> 
    </Route> 
    <Route path="*" component={NotFound} /> 
    </Route> 
</Router> 
); 
export default Routes; 

流れ

<MainContainer>コンポーネントは、私が欲しいどちらも、<TopHeader>コンポーネントと<Sidebar>コンポーネントを表示し、純粋にレイアウトコンポーネントですすべてのページにわたって一貫して永続的です。私<MainContainer>に追加{this.props.children}<AllVideos>等、<Home>ような構成要素を含む私のメインコンテンツに(localhost:3000を介して到達可能な)

<Home>成分を引っ張ってくる成分(コードは、この説明の終わりに向かって設けられている)は、現在、単にプレースホルダ - 私は今のところコンポーネント(localhost:3000/videos経由でアクセス可能)に集中しています。この<MainContainer>コンポーネントは、ComponentWillMount()のアクションをトリガして、AJAXコールを発生させ、これを私の店で受信します。私は私の最初のストアオブジェクトを移入し、私の<AllVideos>コンポーネントに

を私が聞くonChangeイベントを発する<AllVideos>コンポーネントは、最初のAJAX呼び出し(上記のように<MainContainers>部品で作られた)だけでなく、onFilterのリスナーのリスナーを持っていますドロップダウン、入力、チェックボックスなどのフォーム要素(別のネストされたコンポーネント<SearchAndFilter>)を使用して特定のフィルタをユーザーがアクティブにしたときに発生するイベントです。

コンポーネントは、ネストされた<Videos>コンポーネント(現在のところ、ネストされた<VideosBody>コンポーネントには何もしません)を持っています。 iはビデオオブジェクトを介してマッピングするネストされた<VideosBody>成分を持つ入れ子になった<Videos>コンポーネント内

は(映像ごとのデータを表示する)小道具を介して受信され、いくつかの<VideoCard>コンポーネントを作成します。

さらに、<Videos>コンポーネントには、データ/ビデオの単純なクライアント側フィルタリングのためのいくつかのフォーム要素を表示する単一のコンポーネント(すべてのレンダリングされたコンポーネントの上に座っている)があります。

<AllVideos>のネストは、このようにある:私はhttp://localhost:3000を打つとき

<AllVideos> (listens for the initial AJAX event triggered from a parent component as well as Filter events triggered from a child component, passes props of its state to its nested children) 
    <Videos> (currently just relays props received - may do other things later) 
    <VideosBody> (uses props to build the videocards via looping through the data) 
     <SearchAndFilter /> (onchange of form element values, fire-and-forget actions are triggered that are dispatched to the store where a Filter event is emitted once the processing is done (i use `promises` since there are keydown events) 
     <VideoCard /><VideoCard /><VideoCard /><VideoCard /> etc (also has a <Link to> to the <MediaDetails> object 
    </VideosBody> 
    </Videos> 
</AllVideos> 

、 "/" ルートが活性化さ<Home>コンテナ表示罰金されます。

サイドバーから(http://localhost:3000/videos)のビデオ(http://localhost:3000/videos)のリンク(これの中に入れ子になっているすべてのコンポーネント)が表示されます(<Link to>)。それは正常に動作し、私は期待どおりにレンダリングされたビデオを参照してください。私は、コンポーネント内のフォーム要素を使用してデータをフィルタリングしようとします。フィルタリングは機能しますが、冒頭で言及した警告/エラーが表示されます。この警告/エラーは、原因(フィルタイベントが発生したときにトリガされるメソッド)としてonFilter()メソッドを指します。

私が直接http://localhost:3000/videosにヒットしたとき、私のコンポーネントは正しいデータで表示されます。私のフィルタはうまく動作し、エラーや警告は出力されません。それは私が望むのと同じように機能します。

私はルート間を移動するときに出現する問題のようです。私はonFilter()メソッドをログに記録し、フィルタイベントがルートをナビゲートした回数によってトリガされていることに気付きました。もし私がHome > Videosをすれば、それは正しくトリガーされます。私はHomeに戻り、ビデオに戻ってフィルタを再起動すると、今度はonFilter()メソッドが2回呼び出されます(エラーは2回あります)。これは、各ナビゲーションが実行されるごとに増加し続ける。

ビデオと他の場所の間をナビゲートするときに、ComponentWillUnMount()が影響を受けたコンポーネントのいずれかで起動しないように見えました。このメソッドは、イベントリスナーを削除する場所です。ただし、ComponentWillMount()ComponentDidMount()は、すべてのコンポーネントで開始された各ナビゲーションで発生するようです。同様の問題(レイアウト)is described here (sub-routes)

マイMainContainerコンポーネント:

export default class MainContainer extends React.Component{ 
... 
    componentDidMount(){ 
     CatalogActions.GetCatalog(); //gets my initial AJAX call going 
    }; 
... 
render(){ 
     return(
     <div className="container-fluid"> 
      <header className="row primary-header"> 
       <TopHeader /> 
      </header> 
      <aside className="row primary-aside">Aside here </aside> 
      <main className="row"> 
       <Sidebar /> 
       <div className="col-md-9 no-float"> 
        {this.props.children} 
       </div> 
      </main> 

     </div> 
     ); 
    }; 

マイアプリのコンポーネント:

export default class App extends Component { 
constructor(props){ 

    super(props); 

    this.state = { 

    } 
}; 

... 
... 

render() { 
    return (
    <div> 
     {this.props.children} 
    </div> 
    ); 
    }; 
}; 

AllVideosコンポーネント:

export default class AllVideos extends React.Component { 
    constructor(props){ 
     super(props); 
     this.state={ 
      // isVisible: true 
      initialDataUpdated:false, 
      isFiltered: false, 
      "catalog": [] 
     }; 
     this.onChange= this.onChange.bind(this); 
     this.onError=this.onError.bind(this); 
     this.onFilter=this.onFilter.bind(this); 
     this.onFilterError=this.onFilterError.bind(this); 
    }; 
    componentDidMount(){ 
     if(CatalogStore.getList().length>0){ 
      this.setState({ 
      "catalog": CatalogStore.getList()[0].videos 
     }) 
     }   
     CatalogStore.addChangeListener(this.onChange); 
     CatalogStore.addErrorListener(this.onError); 
     CatalogStore.addFilterListener(this.onFilter); 
     CatalogStore.addFilterErrorListener(this.onFilterError); 
    }; 
    componentWillUnMount(){ //this method never gets called 
     CatalogStore.removeChangeListener(this.onChange); 
     CatalogStore.removeErrorListener(this.onError); 
     CatalogStore.removeFilterListener(this.onFilter); 
     CatalogStore.removeFilterListener(this.onFilterError); 
    }; 
    onChange(){ 
     //This gets triggered after my initial AJAX call(triggered in the MainContainer component) succeeds 
    //Works fine and state is set 
     this.setState({ 
      initialDataUpdated: true, 
      "catalog": CatalogStore.getList()[0].videos 
     }) 
    }; 
    onError(str){ 
     this.setState({ 
      initialDataUpdated: false, 
      "catalog": CatalogStore.getList()[0].videos 

     }); 
    }; 
    componentWillMount(){   
    }; 
    onFilter(){//This is the method that the error points to. The setState does seem to work however, but with accumulating warnings and calls (eg 3 calls if i navigate between different components 3 times instead of the correct 1 that happens when i come to the URL directly without any route navigation) 
     this.setState({ 
      //isFiltered: true, 
      catalog: CatalogStore.getFilteredData() 
     }) 

    } 
    onFilterError(){ 
     this.setState({ 
      //isFiltered: false, 
      catalog: CatalogStore.getFilteredData() 
     }) 
    } 

/*componentWillReceiveProps(nextProps){ 
     //console.log(this.props); 
    }; 
    shouldComponentUpdate(){ 
     //return this.state.catalog.length>0 
     return true 
    } 
    componentWillUpdate(){ 
     //console.log("Will update") 
    }  
*/ 
    render(){ 

      return(
      <div style={VideosTabStyle}> 
       This is the All Videos tab. 
       <Videos data={this.state.catalog} /> 

      </div> 
     ); 
    }; 

送出イベントを受信:

_videoStore.dispatchToken = CatalogDispatcher.register(function(payload) { 
    var action = payload.action; 
    switch (action.actionType) { 
     case CatalogConstants.GET_INITIAL_DATA: 
      _videoStore.list.push(action.data); 
      _filteredStore.videos.push(action.data.videos); 
      _filteredStore.music.push(action.data.music); 
      _filteredStore.pictures.push(action.data.pictures); 
      catalogStore.emit(CHANGE_EVENT); 
      break; 
     case CatalogConstants.GET_INITIAL_DATA_ERROR: 
      catalogStore.emit(ERROR_EVENT); 
      break; 
     case CatalogConstants.SEARCH_AND_FILTER_DATA: 
      catalogStore.searchFilterData(action.data).then(function() { 
       //emits the event after processing - works fine 
       catalogStore.emit(FILTER_EVENT); 
      },function(err){ 
       catalogStore.emit(FILTER_ERROR_EVENT); 
      }); 
      break; 
     ..... 
     .....  
     default: 
      return true; 
    }; 
}); 

イベントemiiters:

var catalogStore = Object.assign({}, EventEmitter.prototype, { 
    addChangeListener: function(cb) { 
     this.on(CHANGE_EVENT, cb); 
    }, 
    removeChangeListener: function(cb) { 
     this.removeListener(CHANGE_EVENT, cb); 
    }, 
    addErrorListener: function(cb) { 
     this.on(ERROR_EVENT, cb); 
    }, 
    removeErrorListener: function(cb) { 
     this.removeListener(ERROR_EVENT, cb); 
    }, 

    addFilterListener: function(cb) { 
     this.on(FILTER_EVENT, cb); 
    }, 
    removeFilterListener: function(cb) { 
     this.removeListener(FILTER_EVENT, cb); 
    }, 
    addFilterErrorListener: function(cb) { 
     this.on(FILTER_ERROR_EVENT, cb); 
    }, 
    removeFilterErrorListener: function(cb) { 
     this.removeListener(FILTER_ERROR_EVENT, cb); 
    }, 
    ..... 
    ..... 
} 

答えて

1

私はあなたのコードのいずれかのテストを行うことができないので、私は電話でんだけど、あなたはcomponentWillUnmountに「M」を小文字何か?

+1

ありがとうございました!はい、それは本当に問題でした。私はこの問題を理解しようと1日半を費やしました。そしてそれが事件のちょうど事件だったと思う:) – arunmenon

関連する問題

 関連する問題