fix: resolve undefined project ref in TabsStateContextProvider (#43222)

This pull request refactors how the `TabsStateContextProvider` receives
the project reference and updates related imports for consistency and
maintainability. The main change is to pass `projectRef` explicitly as a
prop instead of fetching it internally, which improves context control
and makes the component easier to test and reuse. Additionally, the PR
updates import paths to use absolute aliases and removes an unused
function.

This fixes an issue which you can replicate by:
1. Go to SQL editor
2. Open any snippet
3. Delete the local storage `supabase_studio_tabs_{project ref}`
4. Refresh the page while still the snippet is open

This will make the snippet to enter in a ghost state where the tab name
is not visible but you see the content.

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Ivan Vasilov
2026-02-27 05:32:01 +01:00
committed by GitHub
parent 0a96a5d0ce
commit e8c309c312
2 changed files with 21 additions and 53 deletions

View File

@@ -1,9 +1,10 @@
import { PropsWithChildren } from 'react'
import { DatabaseSelectorStateContextProvider } from 'state/database-selector'
import { RoleImpersonationStateContextProvider } from 'state/role-impersonation-state'
import { StorageExplorerStateContextProvider } from 'state/storage-explorer'
import { TableEditorStateContextProvider } from 'state/table-editor'
import { TabsStateContextProvider } from 'state/tabs'
import { DatabaseSelectorStateContextProvider } from '@/state/database-selector'
import { RoleImpersonationStateContextProvider } from '@/state/role-impersonation-state'
import { StorageExplorerStateContextProvider } from '@/state/storage-explorer'
import { TableEditorStateContextProvider } from '@/state/table-editor'
import { TabsStateContextProvider } from '@/state/tabs'
type ProjectContextProviderProps = {
projectRef: string | undefined

View File

@@ -1,11 +1,12 @@
import { buildTableEditorUrl } from 'components/grid/SupabaseGrid.utils'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useParams } from 'common'
import { partition } from 'lodash'
import { NextRouter } from 'next/router'
import { type NextRouter } from 'next/router'
import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'
import { proxy, subscribe, useSnapshot } from 'valtio'
import { buildTableEditorUrl } from '@/components/grid/SupabaseGrid.utils'
import { ENTITY_TYPE } from '@/data/entity-types/entity-type-constants'
export const editorEntityTypes = {
table: ['r', 'v', 'm', 'f', 'p'],
sql: ['sql'],
@@ -387,7 +388,7 @@ function createTabsState(projectRef: string) {
onClearDashboardHistory()
router.push(`/project/${router.query.ref}/${editor === 'table' ? 'editor' : 'sql'}`)
},
handleTabDragEnd: (oldIndex: number, newIndex: number, tabId: string, router: any) => {
handleTabDragEnd: (oldIndex: number, newIndex: number, tabId: string, router: NextRouter) => {
// Make permanent if needed
const draggedTab = store.tabsMap[tabId]
if (draggedTab?.isPreview) {
@@ -415,20 +416,20 @@ export type TabsState = ReturnType<typeof createTabsState>
export const TabsStateContext = createContext<TabsState>(createTabsState(''))
export const TabsStateContextProvider = ({ children }: PropsWithChildren) => {
const { data: project } = useSelectedProjectQuery()
const [state, setState] = useState(createTabsState(project?.ref ?? ''))
const { ref: projectRef } = useParams()
const [state, setState] = useState(createTabsState(projectRef ?? ''))
useEffect(() => {
if (typeof window !== 'undefined' && !!project?.ref) {
setState(createTabsState(project?.ref ?? ''))
if (typeof window !== 'undefined' && !!projectRef) {
setState(createTabsState(projectRef ?? ''))
}
}, [project?.ref])
}, [projectRef])
useEffect(() => {
if (typeof window !== 'undefined' && project?.ref) {
if (typeof window !== 'undefined' && projectRef) {
return subscribe(state, () => {
localStorage.setItem(
getTabsStorageKey(project?.ref),
getTabsStorageKey(projectRef),
JSON.stringify({
activeTab: state.activeTab,
openTabs: state.openTabs,
@@ -437,14 +438,14 @@ export const TabsStateContextProvider = ({ children }: PropsWithChildren) => {
})
)
localStorage.setItem(
getRecentItemsStorageKey(project?.ref),
getRecentItemsStorageKey(projectRef),
JSON.stringify({
items: state.recentItems,
})
)
})
}
}, [project?.ref, state])
}, [projectRef, state])
return <TabsStateContext.Provider value={state}>{children}</TabsStateContext.Provider>
}
@@ -472,37 +473,3 @@ export function createTabId<T extends TabType>(type: T, params: CreateTabIdParam
return ''
}
}
// Remove from local storage when feature flag is disabled
export function removeTabsByEditor(ref: string, editor: 'table' | 'sql') {
// Recent items
const recentItems = getSavedRecentItems(ref)
const filteredRecentItems = recentItems.filter((item) =>
editor === 'sql' ? item.type !== 'sql' : item.type === 'sql'
)
localStorage.setItem(
getRecentItemsStorageKey(ref),
JSON.stringify({ items: filteredRecentItems })
)
// Tabs
const tabs = getSavedTabs(ref)
const filteredTabsMap = Object.fromEntries(
Object.entries(tabs.tabsMap).filter(([, tab]) =>
editor === 'sql' ? tab.type !== 'sql' : tab.type === 'sql'
)
)
const filteredOpenTabs = tabs.openTabs.filter((tabId) => filteredTabsMap[tabId])
localStorage.setItem(
getTabsStorageKey(ref),
JSON.stringify({
activeTab: filteredOpenTabs.includes(tabs.activeTab ?? '')
? tabs.activeTab
: filteredOpenTabs[0] ?? null,
openTabs: filteredOpenTabs,
tabsMap: filteredTabsMap,
previewTabId: tabs.previewTabId,
})
)
}