Collection components
Cimpress UI contains several components that display a collection of items of some kind.
Select
and Menu
are both examples of such components.
These item collections can usually be navigated with the keyboard using arrow keys, and many have some form of selection.
Static collections
Section titled “Static collections”A static collection is a collection that does not change over time (it is hard-coded). This is common for components like action menus, where the items are built into the application.
Cimpress UI implements an intuitive JSX-based API for defining collections.
The following example demonstrates how to build a simple static menu:
import { Button, Menu, MenuItem, MenuRoot } from '@cimpress-ui/react';import { IconChevronDown } from '@cimpress-ui/react/icons';
export default function StaticCollectionExample() { return ( <MenuRoot> <Button iconEnd={<IconChevronDown />}>Actions</Button> <Menu> <MenuItem>Open</MenuItem> <MenuItem>Edit</MenuItem> <MenuItem>Delete</MenuItem> </Menu> </MenuRoot> );}
Sections
Section titled “Sections”Sections or groups of items can be constructed by wrapping the items in a section element, as shown in the following example:
import { Button, Menu, MenuItem, MenuRoot, MenuSection } from '@cimpress-ui/react';import { IconChevronDown } from '@cimpress-ui/react/icons';
export default function StaticSectionsCollectionExample() { return ( <MenuRoot> <Button iconEnd={<IconChevronDown />}>Formatting</Button> <Menu> <MenuSection title="Styles"> <MenuItem>Bold</MenuItem> <MenuItem>Underline</MenuItem> </MenuSection> <MenuSection title="Alignment"> <MenuItem>Left</MenuItem> <MenuItem>Middle</MenuItem> <MenuItem>Right</MenuItem> </MenuSection> </Menu> </MenuRoot> );}
Dynamic collections
Section titled “Dynamic collections”A dynamic collection is a collection that is based on data, for example from an API. It may change over time as items are added, updated, or removed from the collection.
Cimpress UI implements a JSX-based interface for dynamic collections, which maps over your data and applies a function for each item to render it.
The following example demonstrates a select component with options based on dynamic data stored in React state:
import { Select, SelectItem } from '@cimpress-ui/react';import { useState } from 'react';
export default function DynamicCollectionExample() { const [fruits] = useState([ { id: 1, name: 'Apple' }, { id: 2, name: 'Banana' }, { id: 3, name: 'Cherry' }, ]);
return ( <Select label="Favourite fruit" items={fruits}> {(item) => <SelectItem>{item.name}</SelectItem>} </Select> );}
As you can see, the items are passed to the items
prop of the top-level component,
which iterates over each item and calls the function passed as children
to the component.
The item object is passed to the function, which then returns a sub-component.
Unique IDs
Section titled “Unique IDs”All items in a dynamic collection must have a unique id
property.
This property is used to determine which items in the collection changed during an update.
Why not just map the data yourself?
Section titled “Why not just map the data yourself?”You may be wondering why we didn’t use animals.map()
in the example above.
In fact, this works just fine, but it’s less performant, and you must remember to provide both a React key
and an id
prop:
import { Select, SelectItem } from '@cimpress-ui/react';import { useState } from 'react';
export default function DynamicMappedCollectionExample() { const [fruits] = useState([ { id: 1, name: 'Apple' }, { id: 2, name: 'Banana' }, { id: 3, name: 'Cherry' }, ]);
return ( <Select label="Favourite fruit"> {fruits.map((item) => ( <SelectItem key={item.id} id={item.id}> {item.name} </SelectItem> ))} </Select> );}
Using the items
prop and providing a render function allows Cimpress UI to automatically cache the results
of rendering each item and avoid re-rendering all items in the collection when only one of them changes.
This has big performance benefits for large collections.
Updating the data
Section titled “Updating the data”When you need to add, remove, or change an item, you can do so using a standard React state update.
Sections
Section titled “Sections”Sections can be built by returning a section component instead of an item component from the top-level item renderer.
Sections also support an items
prop and a render function for their children.
import { Select, SelectItem, SelectSection } from '@cimpress-ui/react';import { useState } from 'react';
export default function DynamicSectionsCollectionExample() { const [foods] = useState([ { id: 1, name: 'Vegetables', items: [ { id: 101, name: 'Carrot' }, { id: 102, name: 'Broccoli' }, { id: 103, name: 'Arugula' }, ], }, { id: 2, name: 'Fruits', items: [ { id: 201, name: 'Apple' }, { id: 202, name: 'Banana' }, { id: 203, name: 'Cherry' }, ], }, ]);
return ( <Select label="Favourite food" items={foods}> {(section) => ( <SelectSection title={section.name} items={section.items}> {(item) => <SelectItem>{item.name}</SelectItem>} </SelectSection> )} </Select> );}
When updating nested data, be sure that all parent items change accordingly.
Items are immutable, so don’t use mutating methods like push
, or replace a property on a parent item.
Virtualization
Section titled “Virtualization”Virtualization is a performance optimization for large lists. Instead of rendering all items to the DOM, only the visible items are rendered. This reduces memory usage and improves rendering performance.
Virtualization can be enabled by setting the isVirtualized
prop to true
. Please note that not every collection component supports virtualization. You can verify if a specific component supports virtualization by looking for the isVirtualized
prop in its API reference.
import { ComboBox, ComboBoxItem, Stack } from '@cimpress-ui/react';
const LARGE_LIST_OF_NUMBERS = Array.from({ length: 2000 }, (_, i) => { const value = (i + 1).toString(); return { id: value, label: value };});
export default function VirtualizedCollectionExample() { return ( <Stack gap={32}> <ComboBox label="Favorite number (virtualized)" items={LARGE_LIST_OF_NUMBERS} isVirtualized> {(item) => <ComboBoxItem>{item.label}</ComboBoxItem>} </ComboBox>
<ComboBox label="Favorite number (non-virtualized)" items={LARGE_LIST_OF_NUMBERS}> {(item) => <ComboBoxItem>{item.label}</ComboBoxItem>} </ComboBox> </Stack> );}