Improve State Management and Location Handling in Dashboard Tabs

- Updated state handling in DashboardTabPanes to ensure null safety for tab state properties.
- Introduced a new utility function to determine if a location belongs to a leaving tab, enhancing tab navigation logic.
- Refactored location comparison logic to improve accuracy when checking for equality between tab states.
- Enhanced NavigationTabsContext to manage same-path query cleanup, improving tab restoration behavior.
This commit is contained in:
Tom Butcher 2026-09-21 00:20:32 +01:00
parent d348d6f2b3
commit 4f6f7cb881
2 changed files with 44 additions and 14 deletions

View File

@ -24,7 +24,7 @@ const toTabLocation = (tabId, source) => ({
pathname: source?.pathname || '/', pathname: source?.pathname || '/',
search: source?.search ?? '', search: source?.search ?? '',
hash: source?.hash ?? '', hash: source?.hash ?? '',
state: source?.state, state: source?.state ?? null,
key: `tab-${tabId}` key: `tab-${tabId}`
}) })
@ -33,7 +33,7 @@ const reuseTabLocation = (tabId, previous, next) => {
previous && previous &&
locationsEqual(previous, next) && locationsEqual(previous, next) &&
previous.key === `tab-${tabId}` && previous.key === `tab-${tabId}` &&
previous.state === next.state (previous.state ?? null) === (next.state ?? null)
) { ) {
return previous return previous
} }
@ -45,7 +45,7 @@ const entryToLocation = (entry, fallbackLocation, tabId) =>
pathname: entry?.pathname || fallbackLocation.pathname, pathname: entry?.pathname || fallbackLocation.pathname,
search: entry?.search ?? fallbackLocation.search ?? '', search: entry?.search ?? fallbackLocation.search ?? '',
hash: entry?.hash ?? fallbackLocation.hash ?? '', hash: entry?.hash ?? fallbackLocation.hash ?? '',
state: fallbackLocation.state state: entry?.state ?? null
}) })
const getTabCurrentEntry = (tab) => const getTabCurrentEntry = (tab) =>
@ -57,6 +57,12 @@ const tabMatchesLocation = (tab, loc) => {
return locationsEqual(entry, loc) return locationsEqual(entry, loc)
} }
const locationBelongsToLeavingTab = (tab, frozen, loc) => {
if (!loc) return false
if (frozen && locationsEqual(frozen, loc)) return true
return tabMatchesLocation(tab, loc)
}
const CachedTabRouteLayout = () => ( const CachedTabRouteLayout = () => (
<TableStateProvider> <TableStateProvider>
<Outlet /> <Outlet />
@ -67,7 +73,7 @@ const areLocationsEqual = (left, right) =>
left === right || left === right ||
(locationsEqual(left, right) && (locationsEqual(left, right) &&
left?.key === right?.key && left?.key === right?.key &&
left?.state === right?.state) (left?.state ?? null) === (right?.state ?? null))
const arePanePropsEqual = (prev, next) => const arePanePropsEqual = (prev, next) =>
prev.tabId === next.tabId && prev.tabId === next.tabId &&
@ -166,14 +172,20 @@ const DashboardTabPanes = () => {
const switchedTabs = Boolean(prevActiveId && activeTabId && prevActiveId !== activeTabId) const switchedTabs = Boolean(prevActiveId && activeTabId && prevActiveId !== activeTabId)
if (switchedTabs) { if (switchedTabs) {
frozenRef.current.set( const previousTab = tabs.find((tab) => tab.id === prevActiveId)
prevActiveId, const previousFrozen = frozenRef.current.get(prevActiveId)
reuseTabLocation( const lastLoc = lastLocationRef.current
if (locationBelongsToLeavingTab(previousTab, previousFrozen, lastLoc)) {
frozenRef.current.set(
prevActiveId, prevActiveId,
frozenRef.current.get(prevActiveId), reuseTabLocation(prevActiveId, previousFrozen, lastLoc)
lastLocationRef.current
) )
) } else if (!previousFrozen && previousTab) {
frozenRef.current.set(
prevActiveId,
entryToLocation(getTabCurrentEntry(previousTab), lastLoc, prevActiveId)
)
}
pendingRestoreRef.current = true pendingRestoreRef.current = true
} }

View File

@ -257,6 +257,13 @@ const applyLocationToTab = (tab, entry) => {
if (!tab.history?.length) { if (!tab.history?.length) {
return { ...tab, history: [entry], historyIndex: 0 } return { ...tab, history: [entry], historyIndex: 0 }
} }
const currentPath = currentEntry?.pathname || '/'
const nextPath = entry.pathname || '/'
if (currentPath === nextPath) {
const history = [...tab.history]
history[tab.historyIndex] = entry
return { ...tab, history }
}
const truncated = tab.history.slice(0, tab.historyIndex + 1) const truncated = tab.history.slice(0, tab.historyIndex + 1)
return { return {
...tab, ...tab,
@ -858,12 +865,17 @@ export const NavigationTabsProvider = ({ children }) => {
if (entriesEqual(restoring.from, entry)) { if (entriesEqual(restoring.from, entry)) {
return return
} }
// A delayed write from the previous tab (viewId, filters, etc.) // Same-path query cleanup on the tab we just opened (action params, etc.)
if (restoring.previousTabId) { if (entry.pathname === restoring.target?.pathname) {
isRestoringRef.current = null
} else if (restoring.previousTabId) {
// A delayed write from the previous tab (viewId, filters, etc.)
applyEntryToTabId(restoring.previousTabId, entry) applyEntryToTabId(restoring.previousTabId, entry)
if (foreign) foreign.entry = entry if (foreign) foreign.entry = entry
return
} else {
return
} }
return
} }
if (shouldIgnoreRestoredLocation(entry)) return if (shouldIgnoreRestoredLocation(entry)) return
@ -877,7 +889,13 @@ export const NavigationTabsProvider = ({ children }) => {
if (entriesEqual(entry, foreign.entry)) { if (entriesEqual(entry, foreign.entry)) {
return return
} }
if (entry.pathname === foreign.entry.pathname) { const activeEntry = getTabCurrentEntry(
tabsRef.current.find((tab) => tab.id === activeTabIdRef.current)
)
if (
entry.pathname === foreign.entry.pathname &&
entry.pathname !== activeEntry?.pathname
) {
applyEntryToTabId(foreign.tabId, entry) applyEntryToTabId(foreign.tabId, entry)
foreign.entry = entry foreign.entry = entry
return return