Files
supabase/apps/studio/hooks/misc/useSchemaQueryState.ts
Ali Waseem 1c2d28d5b3 chore: wrap local storage into helper methods that are safer (#46628)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

- Noticing our code we have many patterns of calling localstorage and
handling those errors
- We should add those in a single well tested file
- Handle those errors in the singleton which makes it easier for us to
debug customer issues. Logger is outputing local storage warnings for
feature we expose
- Side effect of this is random crashes on studio when local storage
isn't available or handled correctly

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Improved browser storage handling across the app for more reliable
persistence and graceful behavior in restricted or non-browser
environments (settings, previews, charts, tabs, sign-in/session flows,
integrations, and UI state).

* **New Features**
* Introduced a safe storage layer to standardize and harden
local/session persistence.

* **Tests**
  * Added comprehensive tests covering the new safe storage behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-04 07:41:28 -06:00

45 lines
1.5 KiB
TypeScript

import { LOCAL_STORAGE_KEYS, safeLocalStorage, useParams } from 'common'
import { parseAsString, useQueryState } from 'nuqs'
import { useEffect, useMemo } from 'react'
/**
* This hook wraps useQueryState because useQueryState imports app router for some reason which breaks the SSR in
* the playwright tests. I've localized the issue to "NODE_ENV='test'" in the playwright tests.
*/
const useIsomorphicUseQueryState = (defaultSchema: string) => {
if (typeof window === 'undefined') {
return [defaultSchema, () => {}] as const
} else {
// eslint-disable-next-line react-hooks/rules-of-hooks
return useQueryState(
'schema',
parseAsString.withDefault(defaultSchema).withOptions({
clearOnDefault: false,
})
)
}
}
export const useQuerySchemaState = () => {
const { ref } = useParams()
const defaultSchema =
ref && ref.length > 0
? safeLocalStorage.getItem(LOCAL_STORAGE_KEYS.LAST_SELECTED_SCHEMA(ref)) || 'public'
: 'public'
// cache the original default schema so that it's not changed by another tab and cause issues in the app (saving a
// table on the wrong schema)
const originalDefaultSchema = useMemo(() => defaultSchema, [ref])
const [schema, setSelectedSchema] = useIsomorphicUseQueryState(originalDefaultSchema)
useEffect(() => {
// Update the schema in local storage on every change
if (ref && ref.length > 0) {
safeLocalStorage.setItem(LOCAL_STORAGE_KEYS.LAST_SELECTED_SCHEMA(ref), schema)
}
}, [schema, ref])
return { selectedSchema: schema, setSelectedSchema }
}