petyosi / react-virtuoso

The most powerful virtual list component for React
https://virtuoso.dev
MIT License
5.25k stars 301 forks source link

Support data prop for grouped mode #235

Open petyosi opened 3 years ago

petyosi commented 3 years ago

Since the flat and the grouped component are the same, there should be a way to detect the grouped mode. Most intuitive would be an array of arrays. Partially reported in #232.

Hideman85 commented 2 years ago

I think it would be better to revise the props of GroupedVirtuoso to something more intuitive like:

interface GroupedVirtuosoProps<T> extends Omit<VirtuosoProps<T>, 'totalCount' | 'itemContent'> {
  groups: {
    items: T[]
    RenderItem: React.FC<{ item: T, index: number }>
    RenderGroup: React.FC
  }[]
}

If people want a wrapper you could use this:

export interface CustomGroupedVirtuosoProps<T> extends Omit<VirtuosoProps<T>, 'totalCount' | 'itemContent'> {
  groups: {
    items: T[]
    RenderItem: React.FC<{ item: T, index: number }>
    RenderGroup: React.FC
  }[]
}

export const CustomGroupedVirtuoso = <T extends unknown>({ groups, ...props }: React.PropsWithChildren<CustomGroupedVirtuosoProps<T>>): React.ReactElement => {
  const virtuosoProps = useMemo<Pick<GroupedVirtuosoProps<T>, 'data' | 'itemContent' | 'groupContent' | 'groupCounts'>>(() => {
    const groupCounts: number[] = []
    let data: T[] = []
    const formattedGroups: (Pick<CustomGroupedVirtuosoProps<T>['groups'][0], 'RenderGroup' | 'RenderItem'> & { firstIndex: number })[] = []

    let size = 0
    groups.forEach(({ items, RenderGroup, RenderItem }) => {
      const prevSize = size
      size += items.length
      groupCounts.push(items.length)
      data = data.concat(items)

      formattedGroups.push({
        firstIndex: prevSize,
        RenderGroup: React.memo(RenderGroup),
        RenderItem: React.memo(RenderItem)
      })
    })

    const itemContent: GroupedVirtuosoProps<T>['itemContent'] = (globalIndex, groupIndex, item) => {
      const { RenderItem, firstIndex } = formattedGroups[groupIndex]
      const index = globalIndex - firstIndex

      return <RenderItem index={index} item={item} />
    }

    const groupContent: GroupedVirtuosoProps<T>['groupContent'] = groupIndex => {
      const { RenderGroup } = formattedGroups[groupIndex]

      return <RenderGroup />
    }

    return { data, itemContent, groupContent, groupCounts }
  }, [groups])

  return <GroupedVirtuoso {...props} {...virtuosoProps} />
}