Direct answer
In Elm 0.19 and later, the Elm Architecture (TEA) is a pattern rather than a pagination framework. It provides no built-in virtual scrolling, automatic query bounding, or page-state helpers. The documented expectation is that the Model is the single source of truth: page index and total count are ordinary fields in the Model record, and fetches are expressed as Cmd values returned from update.
Confirmed facts vs. idiomatic practice
Confirmed: TEA defines Model-View-Update with Cmd for side effects. elm/http provides HTTP requests. The core packages do not include pagination utilities.
Idiomatic (not formally documented): Storing currentPage and totalItems as Int fields in the Model record, wrapping the page payload in a custom type such as Loading | Failure | Success, and disabling navigation controls while a request is in flight.
Modeling page state
Represent the pagination slice in the Model with explicit fields:
type alias Model =
{ currentPage : Int
, totalItems : Int
, itemsPerPage : Int
, pageState : PageState
}
type PageState
= Loading
| Loaded (List Item)
| Failed Http.Error
This keeps the page index and total count in one place and makes the loading/error state visible to the View.
Composing Cmd without duplicate fetches
In update, check the current pageState before issuing a new Cmd. If a request is already Loading, return the existing model with Cmd.none. Otherwise, update the page index and return the fetch command:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
PageChanged newPage ->
case model.pageState of
Loading ->
( model, Cmd.none )
_ ->
( { model | currentPage = newPage, pageState = Loading }
, fetchPage newPage model.itemsPerPage
)
DataReceived (Ok items) ->
( { model | pageState = Loaded items }, Cmd.none )
DataReceived (Err err) ->
( { model | pageState = Failed err }, Cmd.none )
This pattern prevents overlapping requests because the Model acts as a gatekeeper. The View can also disable buttons when pageState is Loading, but the update guard is the reliable boundary.
Client-side slicing
Client-side slicing of a bounded response is technically possible in Elm, but it is not supported by any core guideline and is generally discouraged for large datasets. Elm's immutable lists and persistent data structures make holding a very large collection in the Model memory-intensive and can slow rendering. The safer approach is to pass the page index and page size to the API so the server returns only the needed slice.
One diagnostic question
Does your API return a total item count, or only a next-page cursor? If it returns only a cursor, replace totalItems : Int with nextCursor : Maybe String and adjust the View accordingly.