mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 12:55:06 +03:00
## Context
If you've got a queued operation on a table, then switch tables, the
dashboard stalls for a few seconds before finally rendering the selected
table.
This was caused by an infinite loop in a useEffect calling
`reapplyOptimisticUpdates` due to `dataUpdatedAt` being in the
dependency array (which comes from react query)
Removing `dataUpdatedAt` would resolve the issue, but then it won't
solve the following comment in the code:
`// This ensures pending changes remain visible when switching tabs or
after data refresh`
## Changes involved
Opting to refactor the logic for rendering data in the Grid instead
- Avoid manipulating the data directly from the query client
- Manual changes to the query client are hard to track when debugging
- Ideally the data in the query client are exactly what is coming from
the API
- Instead just compute the row data if there's operations applied, and
render that in the grid
```
const baseRows = data?.rows ?? EMPTY_ARR
const rows = formatGridDataWithOperationValues({ operations, rows:
baseRows })
```
- Simplifies operations logic + data in react query remains as the
source of truth
- This also improves perceived performance, as previously we'd need to
invalidate the query client if we're removing any operation.
- A lot more apparent with the undo operation introduced
[here](https://github.com/supabase/supabase/pull/43957)
- Whereas now, we'll just revert back to whatever's in the query client
(and still do the invalidation behind the scenes) so things feel faster
135 lines
4.6 KiB
TypeScript
135 lines
4.6 KiB
TypeScript
import { keepPreviousData } from '@tanstack/react-query'
|
|
import { useParams } from 'common'
|
|
import { isMsSqlForeignTable } from 'data/table-editor/table-editor-types'
|
|
import { useTableRowsQuery } from 'data/table-rows/table-rows-query'
|
|
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
|
import { RoleImpersonationState } from 'lib/role-impersonation'
|
|
import { EMPTY_ARR } from 'lib/void'
|
|
import { PropsWithChildren, useEffect, useRef, useState } from 'react'
|
|
import { DataGridHandle } from 'react-data-grid'
|
|
import { DndProvider } from 'react-dnd'
|
|
import { HTML5Backend } from 'react-dnd-html5-backend'
|
|
import { createPortal } from 'react-dom'
|
|
import { useRoleImpersonationStateSnapshot } from 'state/role-impersonation-state'
|
|
import { useTableEditorStateSnapshot } from 'state/table-editor'
|
|
import { QueuedOperation } from 'state/table-editor-operation-queue.types'
|
|
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
|
|
|
|
import { useIsTableFilterBarEnabled } from '../interfaces/App/FeaturePreview/FeaturePreviewContext'
|
|
import { Shortcuts } from './components/common/Shortcuts'
|
|
import { Footer } from './components/footer/Footer'
|
|
import { Grid } from './components/grid/Grid'
|
|
import { Header, HeaderProps } from './components/header/Header'
|
|
import { HeaderNew } from './components/header/HeaderNew'
|
|
import { RowContextMenu } from './components/menu/RowContextMenu'
|
|
import { useTableFilter } from './hooks/useTableFilter'
|
|
import { useTableSort } from './hooks/useTableSort'
|
|
import { validateMsSqlSorting } from './MsSqlValidation'
|
|
import { GridProps } from './types'
|
|
import { formatGridDataWithOperationValues } from './utils/queueOperationUtils'
|
|
|
|
export const SupabaseGrid = ({
|
|
customHeader,
|
|
gridProps,
|
|
children,
|
|
}: PropsWithChildren<
|
|
Pick<HeaderProps, 'customHeader'> & {
|
|
gridProps?: GridProps
|
|
}
|
|
>) => {
|
|
const { id: _id } = useParams()
|
|
const tableId = _id ? Number(_id) : undefined
|
|
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const tableEditorSnap = useTableEditorStateSnapshot()
|
|
const snap = useTableEditorTableStateSnapshot()
|
|
const preflightCheck = !tableEditorSnap.tablesToIgnorePreflightCheck.includes(tableId ?? -1)
|
|
|
|
const gridRef = useRef<DataGridHandle>(null)
|
|
const [mounted, setMounted] = useState(false)
|
|
|
|
const newFilterBarEnabled = useIsTableFilterBarEnabled()
|
|
|
|
const { filters } = useTableFilter()
|
|
const { sorts, onApplySorts } = useTableSort()
|
|
|
|
const roleImpersonationState = useRoleImpersonationStateSnapshot()
|
|
|
|
const msSqlWarning = isMsSqlForeignTable(snap.originalTable)
|
|
? validateMsSqlSorting({ filters, sorts, table: snap.originalTable })
|
|
: { warning: null }
|
|
const tableQueriesEnabled = msSqlWarning.warning === null
|
|
|
|
const {
|
|
data,
|
|
error,
|
|
isSuccess,
|
|
isError,
|
|
isPending: isLoading,
|
|
isRefetching,
|
|
} = useTableRowsQuery(
|
|
{
|
|
projectRef: project?.ref,
|
|
tableId,
|
|
sorts,
|
|
filters,
|
|
page: snap.page,
|
|
preflightCheck,
|
|
limit: tableEditorSnap.rowsPerPage,
|
|
roleImpersonationState: roleImpersonationState as RoleImpersonationState,
|
|
},
|
|
{
|
|
placeholderData: keepPreviousData,
|
|
enabled: tableQueriesEnabled,
|
|
retry: (_, error: any) => {
|
|
const doesNotExistError = error && error.message?.includes('does not exist')
|
|
if (doesNotExistError) onApplySorts([])
|
|
return false
|
|
},
|
|
}
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (!mounted) setMounted(true)
|
|
}, [])
|
|
|
|
const operations = tableEditorSnap.operationQueue.operations as QueuedOperation[]
|
|
const baseRows = data?.rows ?? EMPTY_ARR
|
|
const rows = formatGridDataWithOperationValues({ operations, rows: baseRows })
|
|
|
|
const HeaderComponent = newFilterBarEnabled ? HeaderNew : Header
|
|
|
|
return (
|
|
<DndProvider backend={HTML5Backend} context={window}>
|
|
<div className="sb-grid h-full flex flex-col">
|
|
<HeaderComponent
|
|
customHeader={customHeader}
|
|
isRefetching={isRefetching}
|
|
tableQueriesEnabled={tableQueriesEnabled}
|
|
/>
|
|
|
|
{msSqlWarning.warning !== null && <msSqlWarning.Component />}
|
|
|
|
{children || (
|
|
<>
|
|
<Grid
|
|
ref={gridRef}
|
|
{...gridProps}
|
|
rows={rows}
|
|
error={error}
|
|
isDisabled={!tableQueriesEnabled}
|
|
isLoading={isLoading}
|
|
isSuccess={isSuccess}
|
|
isError={isError}
|
|
/>
|
|
<Footer enableForeignRowsQuery={tableQueriesEnabled} />
|
|
<Shortcuts gridRef={gridRef} rows={rows} />
|
|
</>
|
|
)}
|
|
|
|
{mounted && createPortal(<RowContextMenu rows={rows} />, document.body)}
|
|
</div>
|
|
</DndProvider>
|
|
)
|
|
}
|