JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"></script>
<ol id="log">
</ol>

CSS

#log li.first {
  color: green;
}

#log li.second {
  color: blue;
}

#log li.service {
  color: grey;
}

JavaScript

const logDiv = $("#log");
function log(message, cls) {
	logDiv.append($("<li>").text(message).addClass(cls));
}

/* interface IRequest {
	url: string;
	page: number:
	refresh?: boolean
}

interface IEndpoint {
	get(request: IRequest): [];
} */

// Class that represents a cursor into paginated data
function PagedData(endpoint, url) {
	this._endpoint = endpoint;
	this._url = url;
	
	// Our request queue is an observable of structurs of type IRequest
	// We use a reply subject so that the last URL requested is in the stream when the first subscriber subscribes.
	this._requestQueue = new Rx.ReplaySubject(1);
	
	// This is our data observable, subscribe to it to
	// A) receive the last page that this cursor has produced
	// B) receive all future pages
	this.items$ = this._requestQueue
		// Don't re-query unless the "refresh" boolean is true
		.distinctUntilChanged(req => req, (left, right) => right.refresh ? false : left.page == right.page)
		// Make the request...
		.flatMapLatest(request => Rx.Observable.of(request).zip(this._endpoint.get(request)))
		// Wrap data returned with an envelope with data such as which page was requested
		.map(data => {
			const request = data[0];
			const response = data[1];
			return {
				page: request.page,
				url: request.url,
				items: response
			};				
		})
		// Replay last page worth of data on each subscription
		.shareReplay(1);
		
	// Queue up the first page to be retrieved on first subscription
	this.getPage(1);
}

PagedData.prototype.getPage = function(page, refresh) {
	refresh = refresh || false;
	
	// Fire off the workflow
	this._requestQueue.onNext({
		url: this._url,
		refresh: refresh,
		page: page
	});
}

PagedData.prototype.dispose = function() {
	// Question: this should unsubscribe ALL of the subscriptions to this.items$, right?
	this._requestQueue.completed();
}

// -----------------
// EXAMPLE USAGE

var dummyEndpoint = {
	get(request) {
		log(`GET: ${request.url} at page ${request.page}`, "service");
		return...