Skip to content

Instantly share code, notes, and snippets.

@legraphista
Last active July 4, 2024 12:37
Show Gist options
  • Save legraphista/9564bad445d94077edb87818ce2084e7 to your computer and use it in GitHub Desktop.
Save legraphista/9564bad445d94077edb87818ce2084e7 to your computer and use it in GitHub Desktop.
MobX DataFrame for usage with React <Suspense/> api
class Configs extends DataFrame<Config[]> {
constructor() {
super({autoFetch: true});
}
protected async fetch(): Promise<Config[]> {
return fetch(`/config/list`)
}
}
// then in a React.FC use configs.read() where configs is an instance of Configs
import {action, makeObservable, observable} from "mobx";
export type DataFrameOptions = {
autoFetch?: boolean
}
export abstract class DataFrame<T, FetchT = any> {
@observable
fetching: boolean = false;
@observable
data: T | null = null;
@observable.ref
error: Error | null = null;
keepPreviousDataWhiteFetching = true;
private lastUpdateRequest: number = 0;
private promise: Promise<void> | null = null;
constructor(options: DataFrameOptions = {}) {
makeObservable(this);
if (options.autoFetch) {
this.get().catch(action(e => this.error = e));
}
}
protected abstract fetch(): Promise<FetchT>
protected postProcess(fetchedData: FetchT): T {
return fetchedData as unknown as T;
}
@action
private setData(data: DataFrame<T>['data']) {
this.data = data;
}
@action
private setError(error: DataFrame<T>['error']) {
this.error = error;
}
@action
private setFetching(fetching: DataFrame<T>['fetching']) {
this.fetching = fetching;
}
private internalFetch = async () => {
this.setFetching(true);
const myFetchId = ++this.lastUpdateRequest;
try {
if (!this.keepPreviousDataWhiteFetching) {
this.setData(null);
}
this.setError(null);
const rawData = await this.fetch();
if (this.lastUpdateRequest !== myFetchId) {
return console.warn(`Preventing DataFrame ${this.constructor.name} from manifesting outdated results`);
}
this.setData(this.postProcess(rawData));
} catch (e) {
this.setError(e as Error);
this.setData(null);
} finally {
this.setFetching(false);
this.promise = null;
}
}
async get(update: boolean = false): Promise<T> {
if (update || (!this.data && !this.fetching)) {
this.promise = this.internalFetch();
}
await this.promise;
if (this.error) {
throw this.error;
}
return this.data!;
}
read() {
if (this.promise) {
throw this.promise;
}
if (this.error) {
throw this.error;
}
return this.data!;
}
async populate(): Promise<this> {
await this.get();
return this;
}
update = () => this.get(true);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment