Learn how Redux Saga pagination loads chats and messages page by page, with deduplication and a loading guard that stops repeat requests.
How Redux Saga pagination works in a chat app
Redux Saga pagination in React Native means keeping each list in its own Redux slice, fetching pages inside a generator function, and appending only records whose IDs are new. The worker saga reads the current list from the store, requests the next page, then writes back a merged array.
The approach below comes from a 2020 tutorial by Silvio Santos on the Dev doido channel, the Brazilian developer channel that published the three-part realtime chat series. The code is split into actions, a reducer, a saga and two screens for chats and messages.
Every list needs the same three attributes before any request fires: a loading boolean, a total count reported by the API, and the list itself. The reducer sets loading to true when a request starts and false when it succeeds. Total comes from the response payload, not from list.length. That distinction matters because the screen uses list.length < total to decide whether another page exists.
The transcript also introduces React Native Paper as the component library supplying the neumorphic surfaces on the list screens. Screens rely on useSelector to read those three attributes and useDispatch to trigger the fetch.
Separate slices for chats and messages
Chats and messages belong in two separate Redux slices because they load on different screens with different route parameters. A chat list paginates the user's conversations. A message list paginates inside one conversation and needs the chat ID on every request.
The tutorial copies the chat slice to build the message slice and changes three things. The action prefix switches from @list to @message. The request action gains a chat ID alongside the page number. The success action carries messages instead of chats. Keeping the shape identical everywhere else means one reducer template serves both, and the author explicitly says the copy avoids rewriting logic from scratch.
The message slice also drops any field tied to the chat list total, so a failed message fetch cannot corrupt the chat screen's counters. A reset action exists on both slices so a user who pulls to refresh returns to page one with an empty array and a total of zero.
The fetch saga: select, call, put
A worker saga for paging performs three effects in order: read the stored list with select, fetch the page with call, and write the merged result with put. The watcher saga listens for the request action and hands each one to that worker.
The tutorial's worker destructures the next-page flag from the action payload and calls api.get('/chats', { page }). On the response it destructures the received page of records and the server total. Two paths follow. The first page replaces the stored array outright. Every later page goes through a merge step before it is written back.
The imported effects come from redux-saga: call invokes the API function and returns its result to the generator, put dispatches a plain action, and select reads from the store. That last one is what makes the merge possible without passing the old list through the screen.
Deduplicating page two and beyond
Deduplication happens after the concatenation, not before it, because overlap between pages is unpredictable. The tutorial concatenates the stored list with the incoming page, then walks the combined array and keeps records whose ID has not been seen.
In the transcript the author says plainly that simple concatenation is not enough: the same record can arrive in two consecutive responses. Filtering by ID after merging removes those repeats regardless of where the API put them.
The size of a page also matters at the boundary. The screen's next-page check compares list.length against total, so a page that returned duplicates would shrink the effective list below the real count and stall the fetch. Dedupe keeps that comparison honest.
Guarding against duplicate requests
A loading guard stops the list from firing the same page twice while a request is still open. On the screen, the request action only dispatches when the loading flag is false, the incoming page size is below the total, and the current page has more items to show.
The tutorial uses FlatList's onEndReached callback as the trigger. That callback can fire more than once during a fast scroll, so the flag check sits in front of the dispatch rather than inside the saga. The saga itself stays unaware of the gesture.
The same flag drives the UI. A 2020-era React Native app can show an ActivityIndicator while loading and hide it when the flag drops. The tutorial places that indicator outside the list container so it does not unmount mid-scroll.
Ordering reversed message pages
Message pages arrive newest-first from the API, so the tutorial reverses the combined array before storing it. Older messages then sit at the top and the newest sit at the bottom of the conversation view.
A single reverse call covers the whole list on first load. On later pages the same call applies to the merged array, which keeps the descending-to-ascending flip consistent across pagination boundaries.
The transcript also records a caveat about paging older messages upward. After a socket message arrives, the author re-fetches the page rather than inserting the message locally. That choice avoids a duplicate entry when the new message later returns through the paged API, and it keeps the list consistent with a server that does not send message IDs in a predictable sequence.
A current-state check on the stack
Redux Toolkit, released by the Redux team, now installs Redux and Redux Toolkit together and ships createSlice and createAsyncThunk, which many current codebases use in place of hand-written action types. The 2020 tutorial predates that default, so its @list string constants and switch-case reducer still work but represent the older pattern.
Redux Saga remains a maintained generator-based side-effect library and is documented separately from Redux Toolkit. Projects that adopted Toolkit still install redux-saga when they need long-lived watchers, debouncing or ordered side effects.
React Native has moved well past the 2020 navigation and component stack the series used. The paging logic, the select-call-put sequence and the dedupe step do not depend on any of that, which is why the pattern still transfers to a current app.
FAQ
- What is Redux Saga pagination? It is a pattern where a generator function reads the stored list, requests the next page, and writes back a merged array. The page number lives in the action payload, and the store keeps the accumulated results.
- Why is
totalstored separately from the list? The API reports how many records exist, and the screen compares that number againstlist.lengthto decide whether another page is available. A list that dropped duplicates would otherwise look shorter than it is.
- Where does the loading guard belong? In the screen that triggers the request. FlatList's
onEndReachedcan fire more than once during a fast scroll, so checking the loading flag before dispatching is what prevents repeat calls.
- Why deduplicate after concatenation? Overlap between consecutive pages is not predictable in advance. Concatenating first and filtering by ID second removes duplicates wherever they land in the combined array.
- Does this pattern need Redux Toolkit? No. The tutorial uses plain action constants and a switch-case reducer, and both still run. Toolkit's
createSlicewould shorten the boilerplate but does not change the saga logic.
- How are older messages ordered? The API returns them newest-first, so the reducer reverses the merged array before storing it. Older messages then appear at the top of the conversation while the newest sit at the bottom.
- What happens when a socket message arrives? The tutorial re-fetches the current page instead of inserting the message into the list locally. That avoids a duplicate when the same message later arrives through the paged API response.
- Can one saga serve both chats and messages? It can, but the message version needs the chat ID in the request payload. The tutorial keeps them as separate files for that reason and changes only the action prefix, the payload and the response key.
- Should the lists share a slice? No. A failed message fetch should not reset the chat list's counters or loading flag, so the tutorial gives each screen its own state.
- Does this approach work for a live production chat? The logic transfers, but the 2020 tutorial used older React Navigation and React Native Paper versions. Check current framework documentation for API changes before copying the screen code.
From screencast to article
A pagination walkthrough like this one carries small decisions that are easy to lose: dedupe after concatenation, check the flag before dispatching, reverse the merged page. Those details are the part a reader actually needs, and they survive better in text than in a fast screencast.
If you have similar material recorded as a video, Skala Blog turns a YouTube link into a transcription and then a written article. Paste the URL, let the transcript come through, and edit the draft into something searchable. The tutorials at crazyStack cover the same kind of practical development work.