Post

In #Phoenix #LiveView v1.1.0-rc.0 we shipped `:key` support for comprehensions in HEEx. The idea was to finally be able to have change tracking in comprehensions. We did this by leveraging LiveComponents. Sounds easy enough, right? There were some problems though. A thread. #MyElixirStatus

We knew that LiveComponents were not the optimal solution, but they already got us quite far: instead of sending all entries for a list, we only send the list of component ids and the changes for those entries that changed.

When testing this inside a bigger component, we found a big problem though: in some circumstances, the diff could actually get much larger than the regular comprehensions. That's because LiveView already had a very good optimization for comprehensions: template sharing.

When LiveView renders something inside a comprehension, it knows that any static parts inside are rendered multiple times, so it extracts those into a special "template" section, seen as `p` in the diff. The static parts then only refer to a number in that `p` section.

The issue is that due to how live components are handled, those are not part of the usual template sharing. Therefore, if a keyed comprehension rendered a regular comprehension inside, it would send all the static parts for each of the keyed comprehension's entries, leading to very large diffs.

Moreover, regular comprehensions disable change tracking for anything inside. Therefore if you rendered a keyed comprehension inside of a regular one, the benefits of the keyed comprehension could also be negated almost completely.

0 reposts4 likes

When I started the work on `:key`, I knew that the optimal solution would require a major change to how LiveView handles comprehensions. With the LiveComponent approach failing, I bit the bullet and started to rewrite the comprehension code from scratch.

That lead to a quite large PR we shipped in the latest RC.2: All comprehensions are change tracked now. When there's no key, we use the index to track changes. This means that prepending an item leads to all following items being considered changed, so giving a `:key` is still very useful.

The key allows us to efficiently calculate only the changed positions. If entries move without other changes, we can only send the old index. If entries move and change, we send a minimal diff and the old index.