fix(ui): don't crash the playlist list when rows lose their record (#6250)
Some checks failed
Pipeline: Test, Lint, Build / Validate DB migrations (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go plugins (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (Windows) (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Report coverage (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-1 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-2 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-3 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-4 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-5 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-6 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-7 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-8 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-9 (push) Has been cancelled
Pipeline: Test, Lint, Build / Build-10 (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled

* fix(ui): don't crash playlist list rows that lost their record

react-admin 3 evicts records fetched more than 10 minutes ago whenever
another getList for the same resource completes, but the list keeps its
cached ids. The Datagrid then renders those rows with an undefined record,
and the Public and Auto-import switches crashed reading record.id. This
happened when the playlist list was left open and the sidebar or the add
to playlist dialog reloaded a smaller set of playlists.

Both switches now render nothing when the row has no record; the next list
refresh fills the row in again.

* refactor(ui): merge playlist list toggles into one ToggleField

The Public and Auto-import switches were copies that differed only in the
field they flip. ToggleField now flips its source field, and
ToggleAutoImport just shows it for playlists that have a file path. The
tests render inside TestContext, so they use react-admin's real hooks
instead of mocks.
This commit is contained in:
Deluan Quintão 2026-09-29 13:23:36 -04:00 • committed by GitHub
parent e03ce87177
commit 0e1893530b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 34 additions and 37 deletions

View file

@ -67,15 +67,15 @@ const PlaylistFilter = (props) => {
)
}
const TogglePublicInput = ({ resource, source }) => {
export const ToggleField = ({ resource, source }) => {
const record = useRecordContext()
const notify = useNotify()
const [togglePublic] = useUpdate(
const [toggle] = useUpdate(
resource,
record.id,
record?.id,
{
...record,
public: !record.public,
[source]: !record?.[source],
},
{
undoable: false,
@ -86,10 +86,12 @@ const TogglePublicInput = ({ resource, source }) => {
)
const handleClick = (e) => {
togglePublic()
toggle()
e.stopPropagation()
}
if (!record) return null
return (
<Switch
checked={record[source]}
@ -99,35 +101,9 @@ const TogglePublicInput = ({ resource, source }) => {
)
}
const ToggleAutoImport = ({ resource, source }) => {
export const ToggleAutoImport = (props) => {
const record = useRecordContext()
const notify = useNotify()
const [ToggleAutoImport] = useUpdate(
resource,
record.id,
{
...record,
sync: !record.sync,
},
{
undoable: false,
onFailure: (error) => {
notify('ra.page.error', 'warning')
},
},
)
const handleClick = (e) => {
ToggleAutoImport()
e.stopPropagation()
}
return record.path ? (
<Switch
checked={record[source]}
onClick={handleClick}
disabled={!isWritable(record.ownerId)}
/>
) : null
return record?.path ? <ToggleField {...props} /> : null
}
const PlaylistListBulkActions = (props) => {
@ -169,9 +145,7 @@ const PlaylistList = (props) => {
updatedAt: isDesktop && (
<DateField source="updatedAt" sortByOrder={'DESC'} />
),
public: !isXsmall && (
<TogglePublicInput source="public" sortByOrder={'DESC'} />
),
public: !isXsmall && <ToggleField source="public" sortByOrder={'DESC'} />,
comment: <TextField source="comment" />,
sync: !isXsmall && (
<ToggleAutoImport source="sync" sortByOrder={'DESC'} />

View file

@ -1,7 +1,8 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { PlaylistLove } from './PlaylistList'
import { TestContext } from 'ra-test'
import { PlaylistLove, ToggleField, ToggleAutoImport } from './PlaylistList'
vi.mock('../config', () => ({
default: { enableFavourites: true },
@ -32,3 +33,25 @@ describe('<PlaylistLove />', () => {
})
})
})
// react-admin evicts records older than 10 minutes while the list still holds
// their ids, so rows can render with no record.
describe('playlist toggles without a record', () => {
it('<ToggleField /> renders nothing', () => {
const { container } = render(
<TestContext>
<ToggleField resource="playlist" source="public" />
</TestContext>,
)
expect(container.innerHTML).toBe('')
})
it('<ToggleAutoImport /> renders nothing', () => {
const { container } = render(
<TestContext>
<ToggleAutoImport resource="playlist" source="sync" />
</TestContext>,
)
expect(container.innerHTML).toBe('')
})
})