Skip to content

Commit

Permalink
refactor(react)!: query error resetter (#316)
Browse files Browse the repository at this point in the history
  • Loading branch information
tien authored Nov 5, 2024
1 parent e1f2947 commit c4094a1
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 73 deletions.
5 changes: 5 additions & 0 deletions .changeset/real-boxes-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@reactive-dot/react": minor
---

BREAKING: The query error resetter no longer accepts a specific error input; it now defaults to resetting all errors globally.
9 changes: 2 additions & 7 deletions apps/docs/docs/getting-started/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ function QueryWithRefresh() {

## Retry failed query

Error from queries can be caught and reset using `ErrorBoundary` & [`useQueryErrorResetter`](/api/react/function/useQueryErrorResetter) hook.
Error from queries can be reset using `ErrorBoundary` & [`useQueryErrorResetter`](/api/react/function/useQueryErrorResetter) hook.

```tsx
import { useQueryErrorResetter } from "@reactive-dot/react";
Expand All @@ -226,12 +226,7 @@ function AppErrorBoundary() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={(details) => {
if (details.reason === "imperative-api") {
const [error] = details.args;
resetQueryError(error);
}
}}
onReset={() => resetQueryError()}
>
{/* ... */}
</ErrorBoundary>
Expand Down
11 changes: 3 additions & 8 deletions examples/react/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ function Example({ chainName }: ExampleProps) {
<div>
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={(details) => {
if (details.reason === "imperative-api") {
const [error] = details.args;
resetQueryError(error);
}
}}
onReset={() => resetQueryError()}
>
<Suspense fallback={<h2>Loading {chainName}...</h2>}>
<h2>{chainName}</h2>
Expand All @@ -85,13 +80,13 @@ function Example({ chainName }: ExampleProps) {
);
}

function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
function ErrorFallback({ resetErrorBoundary }: FallbackProps) {
return (
<article>
<header>
<strong>Oops, something went wrong!</strong>
</header>
<button type="button" onClick={() => resetErrorBoundary(error)}>
<button type="button" onClick={() => resetErrorBoundary()}>
Retry
</button>
</article>
Expand Down
16 changes: 13 additions & 3 deletions packages/react/src/hooks/use-query-error-resetter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { resetQueryError } from "../utils/jotai.js";
import { atomFamilyErrorsAtom } from "../utils/jotai.js";
import { useAtomCallback } from "jotai/utils";
import { useCallback } from "react";

/**
* Hook for getting function to reset query error caught by error boundary
*
* @returns Function to reset caught query error
*/
// eslint-disable-next-line @eslint-react/hooks-extra/no-redundant-custom-hook
export function useQueryErrorResetter() {
return resetQueryError;
return useAtomCallback(
useCallback((get) => {
const atomFamilyErrors = get(atomFamilyErrorsAtom);

for (const error of atomFamilyErrors) {
error.atomFamily.remove(error.param);
atomFamilyErrors.delete(error);
}
}, []),
);
}
76 changes: 21 additions & 55 deletions packages/react/src/utils/jotai.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,17 @@
import { QueryError } from "@reactive-dot/core";
import type { Atom, Getter } from "jotai";
import { atom, type Atom, type Getter } from "jotai";
import { atomFamily } from "jotai/utils";
import type { AtomFamily } from "jotai/vanilla/utils/atomFamily";
import { Observable } from "rxjs";
import { catchError } from "rxjs/operators";

export class AtomFamilyError extends QueryError {
constructor(
readonly atomFamily: AtomFamily<unknown, unknown>,
readonly param: unknown,
message: string | undefined,
options?: ErrorOptions,
) {
super(message, options);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
static fromAtomFamilyError<TError, TAtomFamily extends AtomFamily<any, any>>(
error: TError,
atomFamily: TAtomFamily,
param: TAtomFamily extends AtomFamily<infer Param, infer _>
? Param
: unknown,
message?: string,
) {
return new this(atomFamily, param, message, {
cause: error,
});
}
}
export const atomFamilyErrorsAtom = atom(
() =>
new Set<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
atomFamily: AtomFamily<any, any>;
param: unknown;
}>(),
);

export function atomFamilyWithErrorCatcher<
TParam,
Expand All @@ -55,38 +38,35 @@ export function atomFamilyWithErrorCatcher<
const atomCatching: TAtomCreator = (read, ...args) => {
// @ts-expect-error complex sub-type
const readCatching: TRead = (...readArgs) => {
const addError = <T>(error: T) => {
const get = readArgs[0] as Getter;
get(atomFamilyErrorsAtom).add({
atomFamily: baseAtomFamily,
param,
});
return error;
};

try {
const value = read(...readArgs);

if (value instanceof Promise) {
return value.catch((error) => {
throw AtomFamilyError.fromAtomFamilyError(
error,
baseAtomFamily,
param,
);
throw addError(error);
});
}

if (value instanceof Observable) {
return value.pipe(
catchError((error) => {
throw AtomFamilyError.fromAtomFamilyError(
error,
baseAtomFamily,
param,
);
throw addError(error);
}),
);
}

return value;
} catch (error) {
throw AtomFamilyError.fromAtomFamilyError(
error,
baseAtomFamily,
param,
);
throw addError(error);
}
};

Expand All @@ -101,17 +81,3 @@ export function atomFamilyWithErrorCatcher<

return baseAtomFamily;
}

export function resetQueryError(error: unknown) {
if (!(error instanceof Error)) {
return;
}

if (error instanceof AtomFamilyError) {
error.atomFamily.remove(error.param);
}

if (error.cause instanceof Error) {
resetQueryError(error.cause);
}
}

0 comments on commit c4094a1

Please sign in to comment.