Dynamic has many relation

Allows e.g. pagination or filtering on HasMany relation.

DynamicHasMany.tsx

import { Component, EntitySubTree, HasMany, SugaredRelativeEntityList, useEntity, useExtendTree } from '@contember/admin'
import React, { ReactNode, useEffect, useRef, useState } from 'react'
import { useObjectMemo } from '@contember/react-utils'
export type DynamicHasManyProps =
& SugaredRelativeEntityList
& { children: ReactNode }
export const DynamicHasMany = Component<DynamicHasManyProps>(({ children, ...props }) => {
const listParams = useObjectMemo(props)
const [initialListParams] = useState(listParams)
const [displayedState, setDisplayedState] = useState<{
treeRoot: undefined | string
list: SugaredRelativeEntityList
} | undefined>(undefined)
const extendTree = useExtendTree()
const entity = useEntity()
const requestRef = useRef(1)
useEffect(() => {
(async () => {
if (initialListParams === listParams || !entity.idOnServer) {
setDisplayedState(undefined)
return
}
const request = ++requestRef.current
const entityNode = (
<EntitySubTree entity={{ entityName: entity.name, where: { id: entity.idOnServer } }}>
<HasMany {...listParams}>
{children}
</HasMany>
</EntitySubTree>
)
const treeRoot = await extendTree(entityNode)
if (request !== requestRef.current) {
return
}
setDisplayedState({ treeRoot, list: listParams })
})()
}, [children, entity.idOnServer, entity.name, extendTree, initialListParams, listParams])
if (!displayedState) {
return (
<HasMany {...initialListParams}>
{children}
</HasMany>
)
}
return (
<EntitySubTree entity={{ entityName: entity.name, where: { id: entity.idOnServer! } }} treeRootId={displayedState.treeRoot}>
<HasMany {...displayedState.list}>
{children}
</HasMany>
</EntitySubTree>
)
}, ({ children, ...props }) => {
return (
<HasMany {...props}>
{children}
</HasMany>
)
})

usage.tsx

import { Button, Component, SugaredRelativeEntityList } from '@contember/admin'
import { HubArticleListingItem } from './HubArticleListingItem'
import React, { useMemo, useState } from 'react'
import { DynamicHasMany } from './DynamicHasMany'
const getArticleProps = (page: number): SugaredRelativeEntityList => {
return {
field: 'articles[publishedAt <= "now"]',
orderBy: 'pinned desc, publishedAt desc',
limit: 5,
offset: (page - 1) * 5,
}
}
export const ArticlePagination = Component(() => {
const [page, setPage] = useState(1)
return (
<>
<div className="space-y-8">
<DynamicHasMany {...useMemo(() => getArticleProps(page), [page])} >
<HubArticleListingItem />
</DynamicHasMany>
</div>
<div className="flex gap-2 justify-center mt-4">
<Button onClick={() => setPage(page - 1)} disabled={page === 1}>Previous page</Button>
<Button onClick={() => setPage(page + 1)}>Next page</Button>
</div>
</>
)
}, () => {
return (
<DynamicHasMany {...getArticleProps(1)} >
<HubArticleListingItem />
</DynamicHasMany>
)
})