# Sorting rows the client holds

> A sequence the client already holds — a component's `T[]` rows parameter, a `List<T>` — sorts with `OrderBy` / `OrderByDescending`, and the key is named by a SELECTOR, never by a string. That is what lets the USER pick the sort: a column already carries its selector, so naming the column names the sort.

<!-- id: query-sorting-in-memory · area: query · stability: preview · html: https://osysharp.com/reference/query/sorting-in-memory/ -->

## Summary        {#summary}
Rows the client already has sort in memory, and the key is a **selector**:

```osy title="the two spellings, both C#" syntax
rows.OrderBy(x => x.Title)     // a key lambda
rows.OrderBy(column.Value)     // the selector passed directly — C#'s method-group form
```

There is no string form. `OrderBy("Title")` does not exist here for the same reason a column's value is not named by
a string: a rename compiles and fails at runtime, and the compiler cannot check what it cannot see.

## Signature      {#signature}
```osy syntax
rows.OrderBy(<selector>)             // ascending
rows.OrderByDescending(<selector>)   // descending

// <selector> is either:
x => x.Field                         // a lambda taking ONE parameter — the row
column.Value                         // a Func<row, key> value
```

The receiver is a sequence the client **holds**: a component's `T[]` parameter, or a `List<T>`. A `.OrderBy` on a
query member is a different thing — see [OrderBy / ThenBy](https://osysharp.com/reference/query/ordering/) — and folds into the server read.

## Description    {#description}

### The selector IS the sort key   {#selector}
Because a selector is a value, the sort key can be chosen at runtime — which is the whole of click-to-sort:

```osy title="a sortable grid, in full" test app=query-sorting-in-memory
entity Report { [MaxLength(80)] string Title; decimal Total; }

class Column<T> { public string Label; public Func<T, string> Value; }

[Composable] component Grid<T>(T[] rows, Column<T>[] columns) {
  Column<T> sortBy = columns[0];
  action SortBy(Column<T> c) { sortBy = c; }
  render {
    Stack {
      Row {
        foreach (var h in columns) {
          Pressable(onClick: () => SortBy(h)) { Text(h.Label); }
        }
      }
      foreach (var r in rows.OrderBy(sortBy.Value)) {
        Row { foreach (var c in columns) { Text(c.Value(r)); } }
      }
    }
  }
}
```

The grid names no entity and no field. It works for every row type, because a [generic class](https://osysharp.com/reference/class/generics/)
carries the selector and the selector carries the key.

### Where it runs, and why you do not choose   {#execution-side}
A sequence **passed in** has already been fetched, so sorting it is in-memory work over rows on screen. A query
member still has a query behind it, so `.OrderBy` on one folds into the **server** read — which is the right answer
when the query is [paged](https://osysharp.com/reference/query/paging/), because sorting the client's window would order the wrong rows.

You never spell that difference. It follows from where the rows came from.

### A runtime key over a server query   {#runtime-key}
Not supported: a server query's `ORDER BY` is compiled into SQL, so its key must be written out. Let the user pick
the column by sorting the rows the client holds, as above. The compiler says so if you try.

## Examples       {#examples}

Descending, and a computed key — anything the selector can express:

```osy title="a computed sort key" syntax
rows.OrderByDescending(r => r.Total)
rows.OrderBy(r => r.Total > 1000 ? "large" : "small")
```

## Errors         {#errors}

| What you wrote | What you get |
|---|---|
| `rows.OrderBy(column.Run)` where `Run` is an `Action` | *needs a selector that RETURNS the key to sort by, and this one returns nothing.* |
| a `Column<User>` selector over `Report` rows | *this selector reads a User, but the rows are Report.* |
| `rows.OrderBy(x => x.Owner)` (an entity) | *a sort key must be a comparable value.* |
| `Report.OrderBy(col.Value)` (a server query) | *a server query's sort key is compiled into SQL … sort the rows the client already holds.* |
| `var copied = liveRows.ToList();` | *'copied' reads the live member 'liveRows', but 'copied' is not itself `live`* — see below. |

⚠ **Deriving from a query needs `live`.** A non-live field is evaluated once, when the component is created, before
the query has loaded — so it keeps an empty value forever, and the page renders with no rows and no error. Write
`live var copied = …`.

## See also       {#see-also}
- [OrderBy / ThenBy](https://osysharp.com/reference/query/ordering/) — `OrderBy`/`ThenBy` on a SERVER query, compiled into SQL
- [Func<T, R>](https://osysharp.com/reference/ui/function-value/) — `Func<T, R>` as a value: what a selector IS
- [Generic classes](https://osysharp.com/reference/class/generics/) — one `Column<T>` for every row type
- [Skip / Take (paging)](https://osysharp.com/reference/query/paging/) — `Skip`/`Take`, and why a paged query sorts server-side
