A management screen may show errors, action history and a statistics list at the same time. Advancing the error list should not move the history list as well. That coupling often comes from sharing one page variable across independent results. Separate retrieval methods in my reporting archive provide the starting point; the same separation belongs in UI state and request contracts.
Pagination state belongs to a result set
The sample uses error IDs from one to twelve and action IDs from 101 to 112. It requests page two of errors and page one of actions, with three records per page. Run it as a .NET 10 console example to see the independent state without involving a UI framework.
var errors = Enumerable.Range(1, 12).ToArray();
var actions = Enumerable.Range(101, 12).ToArray();
static int[] Page(int[] rows, int page, int size)
{
if(page < 1 || size is < 1 or > 100) throw new ArgumentOutOfRangeException();
return rows.Skip((page-1)*size).Take(size).ToArray();
}
Console.WriteLine(string.Join(",", Page(errors,2,3)));
Console.WriteLine(string.Join(",", Page(actions,1,3)));
The outputs are 4,5,6 for errors and 101,102,103 for actions. Subtracting one converts the one-based page number into a zero-based offset. Sharing a paging function is harmless; sharing an unrelated page value is the problem. Real ErrorsFilter and HistoryFilter objects can each own their page, size and relevant predicates.
Reset only the result affected by a filter
Changing the error-message search should move the error result to its first page while preserving history filters and pagination. Separate query parameters such as errorsPage and historyPage can retain the same distinction in browser navigation. Reusing one URL parameter for two meanings makes back and forward behavior difficult to predict.
The total count must use the same filtered scope as the displayed rows. A filtered list paired with an unfiltered pager sends users to empty pages. Deleting the last record on the last page may require clamping to the new final page, but that correction should remain local to the affected result.
Move the operation to the database for real lists
The console code pages small in-memory arrays. A large table should not be downloaded in full merely to perform Skip and Take in the browser. Apply authorization scope and filters on the server, calculate the total, establish a stable order and select the requested page. The response should include paging metadata without unrelated large detail bodies.
Ordering only by CreatedAt leaves ties unresolved. A combination such as CreatedAt and a unique ID produces a deterministic order. It still does not make offset pages a fixed snapshot during concurrent inserts. For highly active datasets or large offsets, keyset pagination is a separate strategy to evaluate.
Older responses must not replace newer searches
Rapid filter changes can complete out of order. Request cancellation or a current-query identifier prevents an old response from overwriting a newer result. Each independent list should also own its loading and failure state. A failed history request should not remove an already displayed error list.
A short debounce can reduce search traffic without necessarily delaying explicit page-button clicks. The server must validate input bounds as well. The sample limits page size but does not protect the offset multiplication against an extreme page number; a real endpoint should use an appropriate bound or checked arithmetic.
Test the absence of coupling
Change only the error page and confirm that history remains unchanged. Then exercise filtering, no matches, deleting a page's final record and two requests completing in reverse order. These checks establish ownership of state during actual interaction, rather than only proving that the screen displays its first page.