From ad69f549b474db6e9a8d8caff3a7c264b463eae2 Mon Sep 17 00:00:00 2001 From: Moritz von Keiser Date: Wed, 27 May 2026 22:40:17 +0000 Subject: [PATCH] fix(GeolocationEditor): handle missing geo-data files gracefully When geo-data JSON files are unavailable (e.g. 404 in SeaTable external apps), both primary and fallback fetch chains now always resolve rather than reject, preventing unhandled promise rejections that left the editor stuck in an infinite loading state or caused TypeError crashes in render methods. Changes: - getLocationData / getCountryData: add .catch(() => null) to the local fallback so the promise always resolves; fix missing `|| {}` guard on config destructuring in getCountryData - transLocationData / transCountryData (mb-editor): guard against null/ undefined input, return [] instead of crashing on null.children - ProvinceEditor, ProvinceCityEditor, LocationEditor (pc + mb): add .catch() handlers so isLoadingData is cleared on fetch failure, and guard .children access with || [] to prevent 'Cannot read properties of undefined' crashes - initLocationSelecting: guard .children with || [] and add name truthy check before calling .includes() so corrupt geo-data entries do not crash the editor Co-Authored-By: Paperclip --- src/GeolocationEditor/index.js | 16 ++++++++-------- .../mb-editor/country-editor.js | 7 +++++-- src/GeolocationEditor/mb-editor/index.js | 4 +++- .../mb-editor/location-editor.js | 4 +++- .../mb-editor/province-city-editor.js | 4 +++- .../mb-editor/province-editor.js | 4 +++- .../pc-editor/location-editor.js | 17 ++++++++++------- .../pc-editor/province-city-editor.js | 13 ++++++++----- .../pc-editor/province-editor.js | 10 +++++++--- 9 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/GeolocationEditor/index.js b/src/GeolocationEditor/index.js index cb096ae9..85e17ea4 100644 --- a/src/GeolocationEditor/index.js +++ b/src/GeolocationEditor/index.js @@ -9,38 +9,38 @@ const GeolocationEditor = forwardRef(({ isMobile, config: propsConfig, ...props const config = useMemo(() => ({ ...window?.dtable, ...propsConfig, }), [propsConfig]); const getLocationData = useCallback(() => { - if (window?.app?.location) return new Promise((resolve, reject) => { + if (window?.app?.location) return new Promise((resolve) => { resolve(window.app.location); }); const { server = '', mediaUrl = '' } = config || {}; return fetch(`${server}${mediaUrl}geo-data/cn-location.json`.replaceAll('//', '/')).then((res) => { // get locations from server return res.json(); }).catch(() => { - // get locations from local + // get locations from local, fall back to empty object if unavailable return fetch('./geo-data/cn-location.json').then(res => { return res.json(); - }); + }).catch(() => null); }); }, [config]); const getCountryData = useCallback((lang) => { - if (lang === 'cn' && window.app.countryListCn) return new Promise((resolve, reject) => { + if (lang === 'cn' && window.app.countryListCn) return new Promise((resolve) => { resolve(window.app.countryListCn); }); - if (lang !== 'cn' && window.app.countryListEn) return new Promise((resolve, reject) => { + if (lang !== 'cn' && window.app.countryListEn) return new Promise((resolve) => { resolve(window.app.countryListEn); }); - const { mediaUrl = '', server = '' } = config; + const { mediaUrl = '', server = '' } = config || {}; const geoFileName = lang === 'cn' ? 'cn-region-location' : 'en-region-location'; return fetch(`${server}${mediaUrl}geo-data/${geoFileName}.json`.replaceAll('//', '/')) .then(res => { return res.json(); }).catch(() => { - // get locations from local + // get locations from local, fall back to null if unavailable return fetch(`./geo-data/${geoFileName}.json`).then(res => { return res.json(); - }); + }).catch(() => null); }).then(res => { const data = res || {}; if (lang === 'cn') { diff --git a/src/GeolocationEditor/mb-editor/country-editor.js b/src/GeolocationEditor/mb-editor/country-editor.js index 87439ba0..df581cd0 100644 --- a/src/GeolocationEditor/mb-editor/country-editor.js +++ b/src/GeolocationEditor/mb-editor/country-editor.js @@ -21,11 +21,14 @@ const CountryEditor = ({ useEffect(() => { getData().then(data => { - locations.current = data; - const continent = data.find(a => a.children.find(b => b.value === oldValue?.country_region || '')); + const safeData = data || []; + locations.current = safeData; + const continent = safeData.find(a => a.children && a.children.find(b => b.value === oldValue?.country_region || '')); const value = [continent?.value || '', oldValue?.country_region || '']; setValue(value); setLoading(false); + }).catch(() => { + setLoading(false); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/GeolocationEditor/mb-editor/index.js b/src/GeolocationEditor/mb-editor/index.js index ec99e0be..0b2d6e54 100644 --- a/src/GeolocationEditor/mb-editor/index.js +++ b/src/GeolocationEditor/mb-editor/index.js @@ -11,6 +11,7 @@ import MapSelectionEditor from './map-selection-editor'; import './index.css'; const transLocationData = (data) => { + if (!data) return []; if (Object.prototype.toString.call(data) === '[object Object]') { const name = data.name; data.label = name; @@ -22,10 +23,11 @@ const transLocationData = (data) => { }); } } - return data.children; + return data.children || []; }; const transCountryData = (data) => { + if (!data) return []; let _data = []; // eslint-disable-next-line for (let key in data) { diff --git a/src/GeolocationEditor/mb-editor/location-editor.js b/src/GeolocationEditor/mb-editor/location-editor.js index 16f6f4fb..ddb497d7 100644 --- a/src/GeolocationEditor/mb-editor/location-editor.js +++ b/src/GeolocationEditor/mb-editor/location-editor.js @@ -28,7 +28,9 @@ const LocationEditor = ({ useEffect(() => { getData().then(data => { - locations.current = data; + locations.current = data || []; + setLoading(false); + }).catch(() => { setLoading(false); }); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/GeolocationEditor/mb-editor/province-city-editor.js b/src/GeolocationEditor/mb-editor/province-city-editor.js index 0ae53f86..e1209d78 100644 --- a/src/GeolocationEditor/mb-editor/province-city-editor.js +++ b/src/GeolocationEditor/mb-editor/province-city-editor.js @@ -21,7 +21,9 @@ const ProvinceCityEditor = ({ useEffect(() => { getData().then(data => { - locations.current = data; + locations.current = data || []; + setLoading(false); + }).catch(() => { setLoading(false); }); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/GeolocationEditor/mb-editor/province-editor.js b/src/GeolocationEditor/mb-editor/province-editor.js index 26553c64..6eb22be1 100644 --- a/src/GeolocationEditor/mb-editor/province-editor.js +++ b/src/GeolocationEditor/mb-editor/province-editor.js @@ -21,7 +21,9 @@ const ProvinceEditor = ({ useEffect(() => { getData().then(data => { - locations.current = data; + locations.current = data || []; + setLoading(false); + }).catch(() => { setLoading(false); }); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/GeolocationEditor/pc-editor/location-editor.js b/src/GeolocationEditor/pc-editor/location-editor.js index 8cf634ae..1128fca2 100644 --- a/src/GeolocationEditor/pc-editor/location-editor.js +++ b/src/GeolocationEditor/pc-editor/location-editor.js @@ -44,7 +44,7 @@ class LocationEditor extends Component { UNSAFE_componentWillMount() { this.props.getData().then(data => { - this.locations = data; + this.locations = data || {}; const { selectedProvince, selectedCity, selectedCounty, selectedItem } = this.initLocationSelecting(this.value); this.setState({ isLoadingData: false, @@ -53,28 +53,31 @@ class LocationEditor extends Component { selectedCounty, selectedItem }); + }).catch(() => { + this.locations = {}; + this.setState({ isLoadingData: false }); }); } initLocationSelecting = (value) => { - let selectedProvince = this.locations.children.find((province) => { - return value.province && value.province.length > 0 && province.name.includes(value.province); + let selectedProvince = (this.locations.children || []).find((province) => { + return value.province && value.province.length > 0 && province.name && province.name.includes(value.province); }); if (!selectedProvince) { return { selectedProvince: null, selectedCity: null, selectedCounty: null, selectedItem: 'province' }; } - let selectedCity = selectedProvince.children.find((city) => { - return value.city && value.city.length > 0 && city.name.includes(value.city); + let selectedCity = (selectedProvince.children || []).find((city) => { + return value.city && value.city.length > 0 && city.name && city.name.includes(value.city); }); if (!selectedCity) { return { selectedProvince, selectedCity: null, selectedCounty: null, selectedItem: 'city' }; } - let selectedCounty = selectedCity.children.find((county) => { - return value.district && value.district.length > 0 && county.name.includes(value.district); + let selectedCounty = (selectedCity.children || []).find((county) => { + return value.district && value.district.length > 0 && county.name && county.name.includes(value.district); }); if (!selectedCounty) { diff --git a/src/GeolocationEditor/pc-editor/province-city-editor.js b/src/GeolocationEditor/pc-editor/province-city-editor.js index 3b5f9318..d9431f57 100644 --- a/src/GeolocationEditor/pc-editor/province-city-editor.js +++ b/src/GeolocationEditor/pc-editor/province-city-editor.js @@ -41,7 +41,7 @@ class ProvinceCityEditor extends Component { componentDidMount() { this.props.getData().then(data => { - this.locations = data; + this.locations = data || {}; const { selectedProvince, selectedCity, selectedItem } = this.initLocationSelecting(this.value); this.setState({ isLoadingData: false, @@ -49,20 +49,23 @@ class ProvinceCityEditor extends Component { selectedCity, selectedItem }); + }).catch(() => { + this.locations = {}; + this.setState({ isLoadingData: false }); }); } initLocationSelecting = (value) => { - let selectedProvince = this.locations.children.find((province) => { - return value.province && value.province.length > 0 && province.name.includes(value.province); + let selectedProvince = (this.locations.children || []).find((province) => { + return value.province && value.province.length > 0 && province.name && province.name.includes(value.province); }); if (!selectedProvince) { return { selectedProvince: null, selectedCity: null, selectedItem: 'province' }; } - let selectedCity = selectedProvince.children.find((city) => { - return value.city && value.city.length > 0 && city.name.includes(value.city); + let selectedCity = (selectedProvince.children || []).find((city) => { + return value.city && value.city.length > 0 && city.name && city.name.includes(value.city); }); if (!selectedCity) { diff --git a/src/GeolocationEditor/pc-editor/province-editor.js b/src/GeolocationEditor/pc-editor/province-editor.js index e86a3822..e23cdff7 100644 --- a/src/GeolocationEditor/pc-editor/province-editor.js +++ b/src/GeolocationEditor/pc-editor/province-editor.js @@ -34,11 +34,15 @@ class ProvinceEditor extends Component { componentDidMount() { this.props.getData().then(data => { - this.locations = data; - this.filteredProvince = this.locations.children; + this.locations = data || {}; + this.filteredProvince = this.locations.children || []; this.setState({ isLoadingData: false, }); + }).catch(() => { + this.locations = {}; + this.filteredProvince = []; + this.setState({ isLoadingData: false }); }); document.addEventListener('keydown', this.onHotKey, true); } @@ -120,7 +124,7 @@ class ProvinceEditor extends Component { if (value.length > 0) { this.provinceReg = new RegExp(value, 'i'); } - this.filteredProvince = this.locations.children.filter((item) => { + this.filteredProvince = (this.locations.children || []).filter((item) => { if (!this.provinceReg) { return true; }