This is an automated email from the ASF dual-hosted git repository. msyavuz pushed a commit to branch msyavuz/fix/infinite-refresh-bug in repository https://gitbox.apache.org/repos/asf/superset.git
commit d983c0dc40f18121b51d6ad50065b717e82cf083 Author: Mehmet Salih Yavuz <[email protected]> AuthorDate: Fri Jan 9 21:15:25 2026 +0300 fix(Tabs): prevent infinite rerenders with nested tabs --- .../src/dashboard/actions/dashboardState.js | 12 ++++++++++-- .../components/gridComponents/Tab/Tab.jsx | 22 ++++++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js index 18b567e20f..afda5554b6 100644 --- a/superset-frontend/src/dashboard/actions/dashboardState.js +++ b/superset-frontend/src/dashboard/actions/dashboardState.js @@ -603,13 +603,21 @@ export function onRefresh( force = false, interval = 0, dashboardId, + isLazyLoad = false, ) { return dispatch => { - dispatch({ type: ON_REFRESH }); + // Only dispatch ON_REFRESH for dashboard-level refreshes + // Skip it for lazy-loaded tabs to prevent infinite loops + if (!isLazyLoad) { + dispatch({ type: ON_REFRESH }); + } + refreshCharts(chartList, force, interval, dashboardId, dispatch).then( () => { dispatch(onRefreshSuccess()); - dispatch(onFiltersRefresh()); + if (!isLazyLoad) { + dispatch(onFiltersRefresh()); + } }, ); }; diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.jsx index 8902e84a46..a52e416a56 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tab/Tab.jsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Fragment, useCallback, memo, useEffect } from 'react'; +import { Fragment, useCallback, memo, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { useDispatch, useSelector } from 'react-redux'; @@ -129,6 +129,9 @@ const Tab = props => { state => state.dashboardState.tabActivationTimes?.[props.id] || 0, ); const dashboardInfo = useSelector(state => state.dashboardInfo); + + // Track which refresh we've already handled to prevent duplicates + const handledRefreshRef = useRef(null); useEffect(() => { if (props.renderType === RENDER_TAB_CONTENT && props.isComponentVisible) { @@ -137,13 +140,20 @@ const Tab = props => { tabActivationTime && lastRefreshTime > tabActivationTime ) { - const chartIds = getChartIdsFromComponent(props.id, dashboardLayout); - if (chartIds.length > 0) { - requestAnimationFrame(() => { + // Create a unique key for this specific refresh + const refreshKey = `${props.id}-${lastRefreshTime}`; + + // Only proceed if we haven't already handled this refresh + if (handledRefreshRef.current !== refreshKey) { + handledRefreshRef.current = refreshKey; + + const chartIds = getChartIdsFromComponent(props.id, dashboardLayout); + if (chartIds.length > 0) { + // Use lazy load flag to avoid updating global refresh time setTimeout(() => { - dispatch(onRefresh(chartIds, true, 0, dashboardInfo.id)); + dispatch(onRefresh(chartIds, true, 0, dashboardInfo.id, true)); }, CHART_MOUNT_DELAY); - }); + } } } }
