2017-02-05 17 views
0

Примечание: Это довольно длинное описание, так что нести со мной. С количеством компонентов, которые повлекут за собой воспроизводимый JSBin или аналогичную реализацию, письменное описание оказалось более безопасным и работоспособным.ReactJS/Router/Flux: Предупреждение: может обновлять только установленный или монтажный компонент

TL; DR

В моей Flux(3.1.2)/React(15.4.2)/React-Router(3.0.2) приложение, я всегда кажется, чтобы получить следующее сообщение об ошибке/предупреждение ТОЛЬКО когда я перемещаться между маршрутами:

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 сервер в интерфейсе служит данные (что-то вроде REST конечной точки), потребляемого моей ReactJS приложения помогшего React-router и Flux.

Исходные данные хранятся в базе данных SQLite, и я возвращаюсь обратно как JSON всякий раз, когда требуется. Например, при загрузке приложения я инициирую вызов AJAX (через Axios) на мой сервер, чтобы вернуть обратно подмножество результатов в JSON, которое затем сохраняется в моем магазине Flux. Компоненты stateful будут использовать эти данные для управления просмотром по мере необходимости.

Мой 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} (код предусмотрен в конце этого описания) компонента будет тянуть в моей основной контент, который включает в себя компоненты, такие как <Home>, <AllVideos> и т.д.

В <Home> компонент (достижимый через localhost:3000) в настоящее время просто заполнитель - Я сосредоточен на компоненте <AllVideos> (достижимый через localhost:3000/videos). Этот компонент <MainContainer> запускает действие в ComponentWillMount(), которое запускает вызов AJAX, который я получу в своем магазине.Я заполнить мой исходный объект хранения и испускают onChange событие, которое я слушаю и в моем <AllVideos> компоненте

<AllVideos> компонент имеет слушателей для начального AJAX вызова (сделано в <MainContainers> компонента, как указано выше), а также слушателей для onFilter событие, которое я испускаю, когда определенные фильтры активируются пользователем, используя элементы формы, такие как выпадающие списки, входы, флажки и т. д. (в отдельном вложенном компоненте, который называется <SearchAndFilter>).

Компонент <AllVideos> имеет вложенный компонент <Videos> (который в настоящее время не выполняет ничего, кроме передачи реквизита его вложенному компоненту <VideosBody>).

Внутри вложенного компонента <Videos> я есть вложенный <VideosBody> компонент, который отображает видео через объект, полученных с помощью реквизита и создает несколько <VideoCard> компонентов (для отображения данных за видео).

Дополнительно в <Videos> компоненте, я есть один компонент (<SearchAndFilter> сидит выше всех оказываемых <VieoCard> компонентов), который будет отображать несколько элементов формы для некоторых простых на стороне клиента фильтрации данных/видео.

<AllVideos> вложенности таким образом:

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

Когда я ударил http://localhost:3000, маршрут «/» активируется и дисплей штраф <Home> контейнера.

Я перемещаю (<Link to>) с боковой панели на видео (http://localhost:3000/videos), в котором отображается компонент (и, следовательно, все вложенные компоненты внутри этого). Он отлично работает, я вижу, что мои видео отображаются как ожидалось. Я пытаюсь отфильтровать данные, используя элементы формы внутри компонента. Фильтрация работает, но я получаю предупреждение/ошибку, о которых я упомянул в начале, что, в свою очередь, указывает на метод onFilter() в качестве виновника (т. Е. Метод, который запускается при выходе события Filter).

Когда я непосредственно ударил http://localhost:3000/videos, мои компоненты отображают правильные данные. Мои фильтры работают нормально, и никаких ошибок или предупреждений не испускают. Он работает так, как я этого хочу.

Это, кажется, проблема, возникающая при перемещении между маршрутами. Я зарегистрировал метод onFilter() и заметил, что событие фильтра инициируется числом раз, когда я прошел маршрут. Если я сделаю Home > Videos, он будет правильно запускаться один раз. Я перехожу обратно в Home и возвращаюсь к видео и повторно запускаю фильтр, и теперь метод onFilter() теперь вызывается дважды (с удвоенной ошибкой). Это увеличивается с каждой выполненной навигацией.

Я заметил, что 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> 
     ); 
    }; 

My App компонент:

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

Я по телефону, так что я не могу делать какие-либо тестирования вашего кода, но что, если вы строчными на «M» в componentWillUnmount?

+1

Спасибо! Да, это была проблема. Я потратил полтора дня, пытаясь понять эту проблему. И думать, что это всего лишь случай случаев :) – arunmenon

 Смежные вопросы

  • Нет связанных вопросов^_^