[FE-2495] fix(studio): ReferenceRecordPeek breaking with JSONB ARRAY data (#42602)

Issue:
- When opening the peek record foreign key viewer, a full screen React
error would happen

Change:
- Uses the preexisting formatters for the `<ReferenceRecordPeek />`
component

To test:
- Create two tables with a foreign key reference. I did `posts <-
comments`
- On your "posts" table, create a column with a jsonb array column and
**add at least one item to the array**
- On your "comments" table, open the foreign row peek and make sure no
errors occur

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

## Summary by CodeRabbit

* **Refactor**
* Optimized grid component performance through improved column rendering
and enhanced caching mechanisms.
* Added specialized formatters for binary and JSON data types to improve
display quality.
* Improved memory efficiency and rendering speed, particularly when
working with large datasets.
  * Streamlined cell rendering logic for more responsive data grids.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Alaister Young authored and GitHub committed 2026-02-09 16:04:39 +08:00
1 parent 18cc6f8fe9
commit 0628f2f07d
1 file changed
+51 -54
@@ -1,7 +1,4 @@
import { PostgresTable } from '@supabase/postgres-meta'
import { Key } from 'lucide-react'
import DataGrid, { Column } from 'react-data-grid'
import { keepPreviousData } from '@tanstack/react-query'
import { useParams } from 'common'
import { COLUMN_MIN_WIDTH } from 'components/grid/constants'
@@ -9,13 +6,20 @@ import {
ESTIMATED_CHARACTER_PIXEL_WIDTH,
getColumnDefaultWidth,
} from 'components/grid/utils/gridColumns'
import { convertByteaToHex } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils'
import { isArrayColumn, isBinaryColumn, isJsonColumn } from 'components/grid/utils/types'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
import { useTableRowsQuery } from 'data/table-rows/table-rows-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { Key } from 'lucide-react'
import { useMemo } from 'react'
import DataGrid, { Column } from 'react-data-grid'
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { BinaryFormatter } from './BinaryFormatter'
import { DefaultFormatter } from './DefaultFormatter'
import { JsonFormatter } from './JsonFormatter'
interface ReferenceRecordPeekProps {
table: PostgresTable
column: string
@@ -44,59 +48,52 @@ export const ReferenceRecordPeek = ({ table, column, value }: ReferenceRecordPee
{ placeholderData: keepPreviousData }
)
const primaryKeys = table.primary_keys.map((x) => x.name)
const primaryKeys = useMemo(() => table.primary_keys.map((x) => x.name), [table.primary_keys])
const columns = (table?.columns ?? []).map((column) => {
const columnDefaultWidth = getColumnDefaultWidth({
dataType: column.data_type,
format: column.format,
} as any)
const columnWidthBasedOnName =
(column.name.length + column.format.length) * ESTIMATED_CHARACTER_PIXEL_WIDTH
const columnWidth =
columnDefaultWidth < columnWidthBasedOnName ? columnWidthBasedOnName : columnDefaultWidth
const isPrimaryKey = primaryKeys.includes(column.name)
const columns = useMemo(() => {
return (table?.columns ?? []).map((column) => {
const columnDefaultWidth = getColumnDefaultWidth({
dataType: column.data_type,
format: column.format,
} as any)
const columnWidthBasedOnName =
(column.name.length + column.format.length) * ESTIMATED_CHARACTER_PIXEL_WIDTH
const columnWidth =
columnDefaultWidth < columnWidthBasedOnName ? columnWidthBasedOnName : columnDefaultWidth
const isPrimaryKey = primaryKeys.includes(column.name)
const res: Column<any> = {
key: column.name,
name: column.name,
resizable: false,
draggable: false,
sortable: false,
width: columnWidth,
minWidth: COLUMN_MIN_WIDTH,
headerCellClass: 'outline-none !shadow-none',
renderHeaderCell: () => (
<div className="flex h-full items-center justify-center gap-x-2">
{isPrimaryKey && (
<Tooltip>
<TooltipTrigger>
<Key size={14} strokeWidth={2} className="text-brand rotate-45" />
</TooltipTrigger>
<TooltipContent side="bottom">Primary key</TooltipContent>
</Tooltip>
)}
<span className="text-xs truncate">{column.name}</span>
<span className="text-xs text-foreground-light font-normal">{column.format}</span>
</div>
),
renderCell: ({ column: col, row }) => {
const value = row[col.name as any]
const formattedValue = column.format === 'bytea' ? convertByteaToHex(value) : value
return (
<div
className={cn(
'flex items-center h-full w-full whitespace-pre',
formattedValue === null && 'text-foreground-lighter'
const res: Column<any> = {
key: column.name,
name: column.name,
resizable: false,
draggable: false,
sortable: false,
width: columnWidth,
minWidth: COLUMN_MIN_WIDTH,
headerCellClass: 'outline-none !shadow-none',
renderHeaderCell: () => (
<div className="flex h-full items-center justify-center gap-x-2">
{isPrimaryKey && (
<Tooltip>
<TooltipTrigger>
<Key size={14} strokeWidth={2} className="text-brand rotate-45" />
</TooltipTrigger>
<TooltipContent side="bottom">Primary key</TooltipContent>
</Tooltip>
)}
>
{formattedValue === null ? 'NULL' : formattedValue}
<span className="text-xs truncate">{column.name}</span>
<span className="text-xs text-foreground-light font-normal">{column.format}</span>
</div>
)
},
}
return res
})
),
renderCell: isBinaryColumn(column.data_type)
? BinaryFormatter
: isJsonColumn(column.data_type) && !isArrayColumn(column.data_type)
? JsonFormatter
: DefaultFormatter,
}
return res
})
}, [table?.columns, primaryKeys])
return (
<>