From 4967f38aa0a7bd3ae5ee61e5c5e48e11be312dee Mon Sep 17 00:00:00 2001 From: MatthewPattell Date: Mon, 1 Jul 2024 14:29:46 +0200 Subject: [PATCH] feat: suspense query hash, remove observable suspense state --- src/make-exported.ts | 3 +- src/suspense-query.ts | 64 ++++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/make-exported.ts b/src/make-exported.ts index cae2753..f21679a 100644 --- a/src/make-exported.ts +++ b/src/make-exported.ts @@ -11,8 +11,9 @@ const makeExported = ( props: { [P in Exclude]?: 'observable' | 'simple' | 'excluded'; }, + shouldExtend = true, ): void => { - store[exportedPropName] = props; + store[exportedPropName] = { ...(shouldExtend ? store?.[exportedPropName] ?? {} : {}), ...props }; }; /** diff --git a/src/suspense-query.ts b/src/suspense-query.ts index d491b6b..ea4fb21 100644 --- a/src/suspense-query.ts +++ b/src/suspense-query.ts @@ -1,4 +1,4 @@ -import { extendObservable, observable, runInAction } from 'mobx'; +import { makeExported } from './make-exported'; import type { TInitStore } from './types'; export interface IPromise extends Promise { @@ -12,6 +12,10 @@ interface ISuspenseQueryParams { errorFields?: string[]; } +interface ISuspenseQueryOptions { + hash?: unknown; +} + interface ISuspenseSubqueryOptions { id: string; hash: unknown; @@ -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' }); } /** @@ -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, ); } + } + + /** + * 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 = (promise: () => Promise): TReturn | undefined => { + public query = ( + promise: () => Promise, + 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 }; }, ); }