Примечание: Это довольно длинное описание, так что нести со мной. С количеством компонентов, которые повлекут за собой воспроизводимый 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);
},
.....
.....
}
Спасибо! Да, это была проблема. Я потратил полтора дня, пытаясь понять эту проблему. И думать, что это всего лишь случай случаев :) – arunmenon