Skip to content

fix(ui): prevents perPage blink #10238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/next/src/views/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ export const renderListView = async (
const clientProps: ListViewClientProps = {
...listViewSlots,
...sharedClientProps,
collectionConfig: clientCollectionConfig,
columnState,
disableBulkDelete,
disableBulkEdit,
Expand Down
2 changes: 0 additions & 2 deletions packages/ui/src/providers/ListQuery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ export type ListQueryProps = {

export type ListQueryContext = {
data: PaginatedDocs
defaultLimit?: number
defaultSort?: Sort
query: ListQuery
refineListData: (args: ListQuery) => Promise<void>
} & ContextHandlers
Expand Down
25 changes: 16 additions & 9 deletions packages/ui/src/views/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export type ListViewSlots = {

export type ListViewClientProps = {
beforeActions?: React.ReactNode[]
collectionConfig?: ClientCollectionConfig // TODO: make this required in the next major version
/**
* @deprecated
* This prop will be removed in the next major version. You can read the collection slug from `collectionConfig.slug` instead.
*/
collectionSlug: string
columnState: Column[]
disableBulkDelete?: boolean
Expand All @@ -73,6 +78,7 @@ export const DefaultListView: React.FC<ListViewClientProps> = (props) => {
beforeActions,
BeforeList,
BeforeListTable,
collectionConfig: collectionConfigFromProps,
collectionSlug,
columnState,
Description,
Expand Down Expand Up @@ -102,19 +108,16 @@ export const DefaultListView: React.FC<ListViewClientProps> = (props) => {
const { getEntityConfig } = useConfig()
const router = useRouter()

const {
data,
defaultLimit: initialLimit,
handlePageChange,
handlePerPageChange,
query,
} = useListQuery()
const { data, handlePageChange, handlePerPageChange, query } = useListQuery()

const { openModal } = useModal()
const { setCollectionSlug, setCurrentActivePath, setOnSuccess } = useBulkUpload()
const { drawerSlug: bulkUploadDrawerSlug } = useBulkUpload()

const collectionConfig = getEntityConfig({ collectionSlug }) as ClientCollectionConfig
// TODO: in the next major version, `collectionConfigFromProps` will be required so no need to get the config from context
// This is because it requires an entire rendering cycle before it is available through context
const collectionConfig =
collectionConfigFromProps || (getEntityConfig({ collectionSlug }) as ClientCollectionConfig)

const { labels, upload } = collectionConfig

Expand Down Expand Up @@ -272,7 +275,11 @@ export const DefaultListView: React.FC<ListViewClientProps> = (props) => {
</div>
<PerPage
handleChange={(limit) => void handlePerPageChange(limit)}
limit={isNumber(query?.limit) ? Number(query.limit) : initialLimit}
limit={
isNumber(query?.limit)
? Number(query.limit)
: collectionConfig?.admin?.pagination?.defaultLimit
}
limits={collectionConfig?.admin?.pagination?.limits}
resetPage={data.totalDocs <= data.pagingCounter}
/>
Expand Down
4 changes: 4 additions & 0 deletions test/admin/collections/Posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export const Posts: CollectionConfig = {
},
],
},
pagination: {
defaultLimit: 5,
limits: [5, 10, 15],
},
meta: {
description: 'This is a custom meta description for posts',
openGraph: {
Expand Down
77 changes: 53 additions & 24 deletions test/admin/e2e/list-view/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,8 @@ describe('List View', () => {
await deleteAllPosts()

await Promise.all(
Array.from({ length: 12 }, async (_, i) => {
if (i < 6) {
Array.from({ length: 6 }, async (_, i) => {
if (i < 3) {
await createPost()
} else {
await createPost({ title: 'test' })
Expand All @@ -437,10 +437,10 @@ describe('List View', () => {

const tableItems = page.locator(tableRowLocator)

await expect(tableItems).toHaveCount(10)
await expect(page.locator('.collection-list__page-info')).toHaveText('1-10 of 12')
await expect(page.locator('.per-page')).toContainText('Per Page: 10')
await page.goto(`${postsUrl.list}?limit=10&page=2`)
await expect(tableItems).toHaveCount(5)
await expect(page.locator('.collection-list__page-info')).toHaveText('1-5 of 6')
await expect(page.locator('.per-page')).toContainText('Per Page: 5')
await page.goto(`${postsUrl.list}?limit=5&page=2`)
await openListFilters(page, {})
await page.locator('.where-builder__add-first-filter').click()
await page.locator('.condition__field .rs__control').click()
Expand All @@ -449,7 +449,8 @@ describe('List View', () => {
await page.locator('.condition__operator .rs__control').click()
await options.locator('text=equals').click()
await page.locator('.condition__value input').fill('test')
await expect(page.locator('.collection-list__page-info')).toHaveText('1-6 of 6')
await page.waitForURL(new RegExp(`${postsUrl.list}\\?limit=5&page=1`))
await expect(page.locator('.collection-list__page-info')).toHaveText('1-3 of 3')
})
})

Expand Down Expand Up @@ -638,53 +639,81 @@ describe('List View', () => {
})

describe('pagination', () => {
test('should use custom admin.pagination.defaultLimit', async () => {
await deleteAllPosts()

await mapAsync([...Array(6)], async () => {
await createPost()
})

await page.goto(postsUrl.list)
await expect(page.locator('.per-page .per-page__base-button')).toContainText('Per Page: 5')
await expect(page.locator(tableRowLocator)).toHaveCount(5)
})

test('should use custom admin.pagination.limits', async () => {
await deleteAllPosts()

await mapAsync([...Array(6)], async () => {
await createPost()
})

await page.goto(postsUrl.list)
await page.locator('.per-page .popup-button').click()
await page.locator('.per-page .popup-button').click()
const options = await page.locator('.per-page button.per-page__button')
await expect(options).toHaveCount(3)
await expect(options.nth(0)).toContainText('5')
await expect(options.nth(1)).toContainText('10')
await expect(options.nth(2)).toContainText('15')
})

test('should paginate', async () => {
await deleteAllPosts()

await mapAsync([...Array(11)], async () => {
await mapAsync([...Array(6)], async () => {
await createPost()
})

await page.reload()
const tableItems = page.locator(tableRowLocator)
await expect(tableItems).toHaveCount(10)
await expect(page.locator('.collection-list__page-info')).toHaveText('1-10 of 11')
await expect(page.locator('.per-page')).toContainText('Per Page: 10')
await expect(page.locator(tableRowLocator)).toHaveCount(5)
await expect(page.locator('.collection-list__page-info')).toHaveText('1-5 of 6')
await expect(page.locator('.per-page')).toContainText('Per Page: 5')
await page.locator('.paginator button').nth(1).click()
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('page=2')
await expect(tableItems).toHaveCount(1)
await expect(page.locator(tableRowLocator)).toHaveCount(1)
await page.locator('.paginator button').nth(0).click()
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('page=1')
await expect(tableItems).toHaveCount(10)
await expect(page.locator(tableRowLocator)).toHaveCount(5)
})

test('should paginate and maintain perPage', async () => {
test('should paginate without resetting selected limit', async () => {
await deleteAllPosts()

await mapAsync([...Array(26)], async () => {
await mapAsync([...Array(16)], async () => {
await createPost()
})

await page.reload()
const tableItems = page.locator(tableRowLocator)
await expect(tableItems).toHaveCount(10)
await expect(page.locator('.collection-list__page-info')).toHaveText('1-10 of 26')
await expect(page.locator('.per-page')).toContainText('Per Page: 10')
await expect(tableItems).toHaveCount(5)
await expect(page.locator('.collection-list__page-info')).toHaveText('1-5 of 16')
await expect(page.locator('.per-page')).toContainText('Per Page: 5')
await page.locator('.per-page .popup-button').click()

await page
.locator('.per-page button.per-page__button', {
hasText: '25',
hasText: '15',
})
.click()

await expect(tableItems).toHaveCount(25)
await expect(page.locator('.per-page .per-page__base-button')).toContainText('Per Page: 25')
await expect(tableItems).toHaveCount(15)
await expect(page.locator('.per-page .per-page__base-button')).toContainText('Per Page: 15')
await page.locator('.paginator button').nth(1).click()
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('page=2')
await expect(tableItems).toHaveCount(1)
await expect(page.locator('.per-page')).toContainText('Per Page: 25')
await expect(page.locator('.collection-list__page-info')).toHaveText('26-26 of 26')
await expect(page.locator('.per-page')).toContainText('Per Page: 15') // ensure this hasn't changed
await expect(page.locator('.collection-list__page-info')).toHaveText('16-16 of 16')
})
})

Expand Down
2 changes: 1 addition & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}
],
"paths": {
"@payload-config": ["./test/_community/config.ts"],
"@payload-config": ["./test/admin/config.ts"],
"@payloadcms/live-preview": ["./packages/live-preview/src"],
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],
Expand Down