Skip to content

Commit

Permalink
feat: suspense query hash, remove observable suspense state
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewPattell committed Jul 1, 2024
1 parent c2a2fff commit 4967f38
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 26 deletions.
3 changes: 2 additions & 1 deletion src/make-exported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ const makeExported = <T extends object>(
props: {
[P in Exclude<keyof T, 'toString'>]?: 'observable' | 'simple' | 'excluded';
},
shouldExtend = true,
): void => {
store[exportedPropName] = props;
store[exportedPropName] = { ...(shouldExtend ? store?.[exportedPropName] ?? {} : {}), ...props };
};

/**
Expand Down
64 changes: 39 additions & 25 deletions src/suspense-query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extendObservable, observable, runInAction } from 'mobx';
import { makeExported } from './make-exported';
import type { TInitStore } from './types';

export interface IPromise<TReturn> extends Promise<TReturn> {
Expand All @@ -12,6 +12,10 @@ interface ISuspenseQueryParams {
errorFields?: string[];
}

interface ISuspenseQueryOptions {
hash?: unknown;
}

interface ISuspenseSubqueryOptions {
id: string;
hash: unknown;
Expand Down Expand Up @@ -55,17 +59,11 @@ class SuspenseQuery {
const defaultInit = store.init?.bind(store);

store.init = () => {
this.isComplete(); // throw error immediately from server side if exist
this.throwError(); // throw error immediately from server side if exist
defaultInit?.();
};

extendObservable(
store,
{ [fieldName]: false },
{
[fieldName]: observable,
},
);
makeExported(store, { [fieldName]: 'simple' });
}

/**
Expand Down Expand Up @@ -94,51 +92,67 @@ class SuspenseQuery {
}

/**
* Detect if suspense is restored from server side:
* - throw error if exist
* - skip run suspense if already completed
* Throw suspense error
*/
protected isComplete(): boolean {
protected throwError(): void {
const value = this.store[this.params.fieldName];
const valueType = typeof value;

// pass error to error boundary
if (valueType !== 'boolean') {
if (value?.error) {
throw this.jsonToError(
new Error((value?.message ?? value?.name) as string),
value as Record<string, any>,
);
}
}

/**
* Detect if suspense is restored from server side:
* - throw error if exist
* - skip run suspense if already completed
*/
protected isComplete(hash: unknown): boolean {
const value = this.store[this.params.fieldName];

// pass error to error boundary
if (value?.error) {
this.throwError();
}

return value === true;
return value?.done === true && value.hash === hash;
}

/**
* Run request
* Save request resolve status
*/
public query = <TReturn>(promise: () => Promise<TReturn>): TReturn | undefined => {
public query = <TReturn>(
promise: () => Promise<TReturn>,
options: ISuspenseQueryOptions = {},
): TReturn | undefined => {
const { hash = '' } = options;
const { fieldName } = this.params;

if (this.isComplete()) {
if (this.isComplete(hash)) {
return;
}

if (this.store[fieldName]?.hash !== hash) {
this.store[fieldName] = { hash, done: false };
this.promise = undefined;
}

if (!this.promise) {
this.promise = promise();

this.promise.then(
() => {
runInAction(() => {
this.store[fieldName] = true;
});
this.store[fieldName] = { hash, done: true };
},
(e) => {
runInAction(() => {
this.errorJson(e);
this.errorJson(e);

this.store[fieldName] = e;
});
this.store[fieldName] = { error: e };
},
);
}
Expand Down

0 comments on commit 4967f38

Please sign in to comment.