Best practices and recommendations

The Gallery is the only control that can create other controls. It has its own scope. These advanced features can lead to unexpected behavior if the Gallery isn't configured correctly. This article covers best practices and recommendations when you're working with Galleries.

It's easy to create unstable behavior if OnChange or OnSelect of child controls modifies the Items of the parent gallery. For example, a Text input in a gallery can have its OnChange property set to:

Patch(GalleryData, ThisItem, {Name: TextInput.Text})

This is usually fine. Most controls will only trigger OnChange when users change their value directly. However, these controls can cause issues because they also trigger OnChange when the system changes their value:

For example, when ComboBox.DefaultSelectedItems changes, it triggers OnChange. Consider a Combo box in a Gallery with DefaultSelectedItems set to First(ThisItem.A) and its OnChange property set to:

UpdateIf(GalleryData, Name = ThisItem.Name, {
    A: Table({
        B: First(Self.SelectedItems).B
    })
})

This expression updates a record in the gallery's data source that matches the current item's name. The change is to replace the A column of the record with a new table.

This causes an infinite loop. When the user changes the value of the Combo box, OnChange is triggered, which updates GalleryData, which changes DefaultSelectedItems (because First(ThisItem.A) refers to a new table), which triggers OnChange again, and so on.

To avoid infinite loops, you can use modern controls or other controls that don't trigger OnChange when their data changes.

Slow performance from patching items

Even if you avoid unwanted loops, patching or updating Gallery items can be slow if the Gallery has many items. The Gallery doesn't just update the rows associated with the changed records. In some cases, it reloads all items. This is because of the loosely structured nature of data sources. It can be difficult for the Gallery to know whether just a single record changed or the entire data source changed.

Gallery.Selected can change unexpectedly

The Selected property of the Gallery is a moving target. It can change without user interaction when:

  • Gallery.Default changes
  • Gallery.Items changes
  • Refreshing or updating data sources

This might not be desirable for your scenario. If you want a stable copy of the item that is selected by the user, consider storing it in a variable. For example, set the OnSelect property of the Gallery to a global variable CurrentItem:

Set(CurrentItem, Self.Selected)

You can then use CurrentItem in other parts of the app to refer to the most recent item selected by the user rather than by the system.

Don't use Gallery.Selected in a child control's event

The Selected property of the Gallery changes when a user selects an item. However, this event isn't related to events of child controls. Referring to Gallery.Selected in a child control's events can lead to unexpected results.

For example, when a user selects a Checkbox in a Gallery, the following events occur:

  • Checkbox.OnSelect
  • Checkbox.OnCheck
  • Gallery.Selected changes to the newly clicked row

The order of these events isn't fixed. This is a problem if Checkbox.OnSelect is set to:

Notify(Gallery.Selected.Name)

Gallery.Selected might still be referring to the previously selected row when Checkbox.OnSelect is executed.

To avoid timing issues, don't use Gallery.Selected in events. If you must, use Gallery.OnSelect to respond to changes to Gallery.Selected.

Setting Gallery.Items to a variable or output of a Canvas component can give unexpected results, depending on when the variable is set or changed.

Galleries need to know the schema of its Items when the app loads. A schema, also known as shape, is the name and type of columns in a data source. Consider this table:

[{A: "abc", B: 123}, {A: "def", B: 456}]

Its schema consists of a text column A and a number column B.

Most of the time, the Gallery can guess the schema of its Items from the data source and expressions used in the app. But if the Items property is set to the output of a Canvas component or Import control, the Gallery can't determine its schema. The output table from these controls might not be available when the app loads and the schema might even change. The Gallery won't render any items when it doesn't know its schema.

The same issue might happen when Items is set to a variable that's not initialized when the app loads.

As a workaround, you can hint the expected schema to the Gallery with a variable. Set App.OnStart to:

If(false, Set(GalleryData, [{A: "abc", B: 123}]), Set(GalleryData, []))

This lets the system know the schema of the GalleryData table. Then, you can use GalleryData as the Items property of the Gallery. Change it to the actual data source when needed.

Don't assume AllItems contains all items of its dataset

The AllItems property are the items that are loaded into view in a gallery. It's not all items in Items. AllItems can change when the user scrolls the gallery to load more items. Typically, this property is used to get values of child controls when the user is interacting with them. Hence, AllItems is guaranteed to have loaded that item and it's safe to refer to it. Don't refer to an item in AllItems if you're not sure whether the user has seen it.

Similarly, AllItemsCount is the number of items that are loaded into view in the gallery. It's not the total number of records in Items. To get the total records in Items, use CountRows(<expression used for Items property>).

Next steps

Isolate issues in canvas apps