From e8c309c312e53643ea249e42e4c36f23c8efc5ff Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Fri, 27 Feb 2026 05:32:01 +0100 Subject: [PATCH] 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 --- .../layouts/ProjectLayout/ProjectContext.tsx | 11 ++-- apps/studio/state/tabs.tsx | 63 +++++-------------- 2 files changed, 21 insertions(+), 53 deletions(-) diff --git a/apps/studio/components/layouts/ProjectLayout/ProjectContext.tsx b/apps/studio/components/layouts/ProjectLayout/ProjectContext.tsx index 1ac054e7c8e..a796c54102e 100644 --- a/apps/studio/components/layouts/ProjectLayout/ProjectContext.tsx +++ b/apps/studio/components/layouts/ProjectLayout/ProjectContext.tsx @@ -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 diff --git a/apps/studio/state/tabs.tsx b/apps/studio/state/tabs.tsx index 6a136cfbe69..2ded8c04b08 100644 --- a/apps/studio/state/tabs.tsx +++ b/apps/studio/state/tabs.tsx @@ -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 export const TabsStateContext = createContext(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 {children} } @@ -472,37 +473,3 @@ export function createTabId(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, - }) - ) -}