[Maps] fix maps listing page 'Cannot update a component while rendering a different component' (#155545)

To view problem
1) Install sample data set
2) Open maps listing page. Notice the following console warning
<img width="400" alt="Screen Shot 2023-04-21 at 9 19 16 AM"
src="https://user-images.githubusercontent.com/373691/233705813-75efc248-d0e4-40da-bc26-aa01b2e21b00.png">

PR removes side effect during `MapsListViewComp` render that caused
[ScreenReaderRouteAnnouncements](0cd7973e1f/packages/core/chrome/core-chrome-browser-internal/src/ui/header/screen_reader_a11y.tsx)
to update state.

PR also refactors LoadListAndRender into a functional component with
hooks. That change is not needed but was done to isolate the issue.
Figured its worth keeping.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2023-04-21 18:14:57 -06:00 committed by GitHub
parent 085686fceb
commit d4f2639377
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 42 additions and 51 deletions

View file

@ -5,12 +5,10 @@
* 2.0.
*/
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import React, { useState, useEffect } from 'react';
import { Redirect } from 'react-router-dom';
import { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public';
import { ScopedHistory } from '@kbn/core/public';
import { getToasts } from '../../kibana_services';
import { MapsListView } from './maps_list_view';
import { APP_ID } from '../../../common/constants';
import { mapsClient } from '../../content_management';
@ -20,49 +18,39 @@ interface Props {
stateTransfer: EmbeddableStateTransfer;
}
export class LoadListAndRender extends Component<Props> {
_isMounted: boolean = false;
state = {
mapsLoaded: false,
hasSavedMaps: null,
};
export function LoadListAndRender(props: Props) {
const [mapsLoaded, setMapsLoaded] = useState(false);
const [hasSavedMaps, setHasSavedMaps] = useState(true);
componentDidMount() {
this._isMounted = true;
this.props.stateTransfer.clearEditorState(APP_ID);
this._loadMapsList();
useEffect(() => {
props.stateTransfer.clearEditorState(APP_ID);
let ignore = false;
mapsClient
.search({ limit: 1 })
.then((results) => {
if (!ignore) {
setHasSavedMaps(results.hits.length > 0);
setMapsLoaded(true);
}
})
.catch((err) => {
if (!ignore) {
setMapsLoaded(true);
setHasSavedMaps(false);
}
});
return () => {
ignore = true;
};
// only run on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!mapsLoaded) {
// do not render loading state to avoid UI flash when listing page is displayed
return null;
}
componentWillUnmount() {
this._isMounted = false;
}
async _loadMapsList() {
try {
const results = await mapsClient.search({ limit: 1 });
if (this._isMounted) {
this.setState({ mapsLoaded: true, hasSavedMaps: !!results.hits.length });
}
} catch (err) {
if (this._isMounted) {
this.setState({ mapsLoaded: true, hasSavedMaps: false });
getToasts().addDanger({
title: i18n.translate('xpack.maps.mapListing.errorAttemptingToLoadSavedMaps', {
defaultMessage: `Unable to load maps`,
}),
text: `${err}`,
});
}
}
}
render() {
const { mapsLoaded, hasSavedMaps } = this.state;
if (mapsLoaded) {
return hasSavedMaps ? <MapsListView history={this.props.history} /> : <Redirect to="/map" />;
} else {
return null;
}
}
return hasSavedMaps ? <MapsListView history={props.history} /> : <Redirect to="/map" />;
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback, memo } from 'react';
import React, { useCallback, memo, useEffect } from 'react';
import type { SavedObjectsFindOptionsReference, ScopedHistory } from '@kbn/core/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
@ -72,8 +72,14 @@ function MapsListViewComp({ history }: Props) {
const listingLimit = getUiSettings().get(SAVED_OBJECTS_LIMIT_SETTING);
const initialPageSize = getUiSettings().get(SAVED_OBJECTS_PER_PAGE_SETTING);
getCoreChrome().docTitle.change(APP_NAME);
getCoreChrome().setBreadcrumbs([{ text: APP_NAME }]);
// TLDR; render should be side effect free
//
// setBreadcrumbs fires observables which cause state changes in ScreenReaderRouteAnnouncements.
// wrap chrome updates in useEffect to avoid potentially causing state changes in other component during render phase.
useEffect(() => {
getCoreChrome().docTitle.change(APP_NAME);
getCoreChrome().setBreadcrumbs([{ text: APP_NAME }]);
}, []);
const findMaps = useCallback(
async (

View file

@ -20560,7 +20560,6 @@
"xpack.maps.mapActions.removeFeatureError": "Impossible de retirer la fonctionnalité de lindex.",
"xpack.maps.mapListing.entityName": "carte",
"xpack.maps.mapListing.entityNamePlural": "cartes",
"xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "Impossible de charger les cartes",
"xpack.maps.mapSavedObjectLabel": "Carte",
"xpack.maps.mapSettingsPanel.addCustomIcon": "Ajouter une icône personnalisée",
"xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "Ajuster automatiquement la carte aux limites de données",

View file

@ -20560,7 +20560,6 @@
"xpack.maps.mapActions.removeFeatureError": "インデックスから機能を削除できません。",
"xpack.maps.mapListing.entityName": "マップ",
"xpack.maps.mapListing.entityNamePlural": "マップ",
"xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "マップを読み込めません",
"xpack.maps.mapSavedObjectLabel": "マップ",
"xpack.maps.mapSettingsPanel.addCustomIcon": "カスタムアイコンを追加",
"xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "自動的にマップをデータ境界に合わせる",

View file

@ -20560,7 +20560,6 @@
"xpack.maps.mapActions.removeFeatureError": "无法从索引中移除特征。",
"xpack.maps.mapListing.entityName": "地图",
"xpack.maps.mapListing.entityNamePlural": "地图",
"xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "无法加载地图",
"xpack.maps.mapSavedObjectLabel": "地图",
"xpack.maps.mapSettingsPanel.addCustomIcon": "添加定制图标",
"xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "使地图自动适应数据边界",