fix: storage view is not reset when switching buckets (#43370)

## Problem

When navigating between buckets after having selected a folder, the
storage view shows the previous folder content.

## Solution

Set the selected bucket before navigating to it, preventing a race
condition with Valtio updates and the effect that loads the bucket
content.

## How to test

1. Create 2 buckets
2. In one of them, create a folder and upload some files
3. Go back to the bucket list
4. Open the other bucket

Results:
- It should not show the previous bucket content anymore
- It should not show an empty 2nd column either

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Gildas GarciaandJoshen Lim authored and GitHub committed 2026-03-05 12:10:46 +01:00
1 parent e0a736181a
commit 9ce3bdf552
3 files changed
+54 -10

No files matched your search

@@ -3,7 +3,6 @@ import { useEffect, useRef, useState } from 'react'
import { useStaticEffectEvent } from '@/hooks/useStaticEffectEvent'
import { useDebounce } from '@uidotdev/usehooks'
import { useParams } from 'common'
import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query'
import type { Bucket } from 'data/storage/buckets-query'
import { useLatest } from 'hooks/misc/useLatest'
@@ -20,9 +19,9 @@ import { MoveItemsModal } from './MoveItemsModal'
import { PreviewPane } from './PreviewPane'
export const StorageExplorer = () => {
const { ref, bucketId } = useParams()
const storageExplorerRef = useRef(null)
const {
bucketId,
projectRef,
view,
columns,
@@ -44,7 +43,7 @@ export const StorageExplorer = () => {
setSelectedItemsToMove,
} = useStorageExplorerStateSnapshot()
useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM })
useProjectStorageConfigQuery({ projectRef }, { enabled: IS_PLATFORM })
const { data: bucket, isLoading: isBucketQueryLoading } = useSelectedBucket()
// Detect when transitioning between buckets to avoid showing stale content from the previous bucket.
@@ -91,14 +90,15 @@ export const StorageExplorer = () => {
}
}
})
useEffect(() => {
if (bucket && projectRef) fetchContents(bucket)
}, [bucket, projectRef, debouncedSearchString, fetchContents])
}, [bucketId, bucket, projectRef, debouncedSearchString, fetchContents])
const openBucketRef = useLatest(openBucket)
useEffect(() => {
if (bucket && !!projectRef) openBucketRef.current(bucket)
}, [bucket, projectRef, openBucketRef])
}, [bucketId, bucket, projectRef, openBucketRef])
/** Checkbox selection methods */
/** [Joshen] We'll only support checkbox selection for files ONLY */
+17 -2
View File
@@ -1,7 +1,7 @@
import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js'
import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'common'
import { capitalize, chunk, compact, find, findIndex, has, isObject, uniq, uniqBy } from 'lodash'
import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'
import { createContext, PropsWithChildren, useContext, useEffect, useRef, useState } from 'react'
import { useLatest } from 'react-use'
import { toast } from 'sonner'
import * as tus from 'tus-js-client'
@@ -50,6 +50,7 @@ import { tryParseJson } from '@/lib/helpers'
import { lookupMime } from '@/lib/mime'
import { createProjectSupabaseClient } from '@/lib/project-supabase-client'
import { ResponseError } from '@/types'
import { useSelectedBucket } from '@/components/interfaces/Storage/FilesBuckets/useSelectedBucket'
type UploadProgress = {
percentage: number
@@ -82,11 +83,13 @@ function createStorageExplorerState({
connectionString,
resumableUploadUrl,
clientEndpoint,
bucketId,
}: {
projectRef: string
connectionString: string
resumableUploadUrl: string
clientEndpoint: string
bucketId?: string
}) {
const localStorageKey = LOCAL_STORAGE_KEYS.STORAGE_PREFERENCE(projectRef)
const { view, sortBy, sortByOrder, sortBucket } =
@@ -95,6 +98,7 @@ function createStorageExplorerState({
const state = proxy({
projectRef,
bucketId,
connectionString,
resumableUploadUrl,
uploadProgresses: [] as UploadProgress[],
@@ -1864,10 +1868,13 @@ const StorageExplorerStateContext = createContext<StorageExplorerState>(
export const StorageExplorerStateContextProvider = ({ children }: PropsWithChildren) => {
const { data: project } = useSelectedProjectQuery()
const { data: bucket } = useSelectedBucket()
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
const [state, setState] = useState(() => createStorageExplorerState(DEFAULT_STATE_CONFIG))
const stateRef = useLatest(state)
const bucketRef = useRef(bucket?.id)
const {
storageEndpoint,
@@ -1883,13 +1890,20 @@ export const StorageExplorerStateContextProvider = ({ children }: PropsWithChild
useEffect(() => {
const hasDataReady = !!project?.ref
const storeAlreadyLoaded = state.projectRef === project?.ref
const hasBucketChanged = bucket?.id !== bucketRef.current
if (!isPaused && hasDataReady && !storeAlreadyLoaded && isSuccessSettings) {
if (
!isPaused &&
hasDataReady &&
isSuccessSettings &&
(!storeAlreadyLoaded || hasBucketChanged)
) {
const clientEndpoint = storageEndpoint ?? hostEndpoint ?? ''
const resumableUploadUrl = `${clientEndpoint}/storage/v1/upload/resumable`
setState(
createStorageExplorerState({
projectRef: project?.ref ?? '',
bucketId: bucket?.id,
connectionString: project.connectionString ?? '',
resumableUploadUrl,
clientEndpoint,
@@ -1897,6 +1911,7 @@ export const StorageExplorerStateContextProvider = ({ children }: PropsWithChild
)
}
}, [
bucket?.id,
state.projectRef,
project?.ref,
project?.connectionString,
+32 -3
View File
@@ -1,8 +1,7 @@
import { expect } from '@playwright/test'
import path from 'path'
import { expect } from '@playwright/test'
import { env } from '../env.config.js'
import { test } from '../utils/test.js'
import { waitForApiResponse } from '../utils/wait-for-response.js'
import {
createBucket,
createFolder,
@@ -18,6 +17,8 @@ import {
createBucket as createBucketViaApi,
deleteBucket as deleteBucketViaApi,
} from '../utils/storage/index.js'
import { test } from '../utils/test.js'
import { waitForApiResponse } from '../utils/wait-for-response.js'
const bucketNamePrefix = 'pw_bucket'
@@ -291,6 +292,34 @@ test.describe('Storage', () => {
).toBeVisible()
})
test('resets storage view when switching buckets', async ({ page, ref }) => {
const bucketName = `${bucketNamePrefix}_navigation`
const bucketName2 = `${bucketNamePrefix}2_navigation`
const folderName = 'folder_navigation'
const fileName = 'test-file.txt'
// Create 2 bucket via API, navigate to the first
await deleteBucketViaApi(bucketName)
await deleteBucketViaApi(bucketName2)
await createBucketViaApi(bucketName, false)
await createBucketViaApi(bucketName2, false)
await navigateToStorageFiles(page, ref)
await navigateToBucket(page, ref, bucketName)
// create a folder and add a file
await createFolder(page, folderName)
// Open the folder
await page.getByTitle(folderName).click()
const filePath = path.join(import.meta.dirname, 'files', fileName)
await uploadFile(page, filePath, fileName)
// Navigate to bucket list
await page.getByRole('link', { name: 'Files' }).nth(1).click()
// Navigate to the 2nd bucket
await navigateToBucket(page, ref, bucketName2)
await expect(page.getByTitle(fileName)).not.toBeVisible()
})
test('can delete a file', async ({ page, ref }) => {
const bucketName = `${bucketNamePrefix}_delete_file`
const fileName = 'test-file.txt'