Skip to content

Commit

Permalink
Merge pull request #13 from Optum/useLoadData_error_edge_case
Browse files Browse the repository at this point in the history
handle `useLoadData` synchronous exception edge case
  • Loading branch information
NielsJPeschel authored Feb 3, 2024
2 parents 26742ad + c7ff7a5 commit 08e5dca
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 18 deletions.
13 changes: 13 additions & 0 deletions hooks/useLoadData/useLoadData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,17 @@ describe('useLoadData', () => {
expect(getFail).toHaveBeenCalledTimes(2);
expect(mockRetry).toHaveBeenCalledTimes(0);
});

it('should set isError to true if the fetch data function throws a non-promise exception', () => {
const {result} = renderHook(() => {
return useLoadData(() => {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw 'immediate failure';
});
});

expect(result.current.isInProgress).toBe(false);
expect(result.current.isError).toBe(true);
expect(result.current.error).toBe('immediate failure');
});
});
63 changes: 45 additions & 18 deletions hooks/useLoadData/useLoadData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,29 +163,56 @@ export function useLoadData<T extends NotUndefined, Deps extends any[]>(
const initialPromise = useMemo(() => {
const correctedArgs = correctOptionalDependencies(fetchDataArgs);
if (!data && counter < 1 && checkArgsAreLoaded(correctedArgs)) {
return fetchData(...((correctedArgs.map(unboxApiResponse) || []) as Parameters<typeof fetchData>));
try {
return {
res: fetchData(...((correctedArgs.map(unboxApiResponse) || []) as Parameters<typeof fetchData>)),
error: undefined
};
} catch (e) {
return {
res: undefined,
error: e
};
}
} else {
return undefined;
return {res: undefined, error: undefined};
}
}, [counter]);

const nonPromiseResult = initialPromise instanceof Promise ? undefined : initialPromise;
const nonPromiseResult = initialPromise.res instanceof Promise ? undefined : initialPromise.res;
const initialData = data || nonPromiseResult;

// Initialize our pending data to one of three possible states:
// 1. If initial data was supplied or if the fetchData function returned a non-Promise value,
// then our initial state will be already "resolved" (not in-progress and not error, we already have the result)
// 2. If initial data was not supplied and fetchData returned a Promise, then our initial state is in-progress
// 3. If initial data was not supplied and fetchData threw a *synchronous* (non-Promise) exception,
// then our initial state is "rejected" (not in-progress and already has an error value)
const initialDataResolved =
initialData &&
({
isInProgress: false,
isError: false,
result: initialData,
error: undefined
} as const);
const initialDataRejected =
initialPromise.error !== undefined &&
({
isInProgress: false,
isError: true,
result: undefined,
error: initialPromise.error
} as const);
const initialDataPending = {
isInProgress: true,
isError: false,
result: undefined,
error: undefined
} as const;

const [pendingData, setPendingData] = useState<ApiResponse<T>>(
initialData
? {
isInProgress: false,
isError: false,
result: initialData,
error: undefined
}
: {
isInProgress: true,
isError: false,
result: undefined,
error: undefined
}
initialDataResolved || initialDataRejected || initialDataPending
);

function retry() {
Expand Down Expand Up @@ -224,9 +251,9 @@ export function useLoadData<T extends NotUndefined, Deps extends any[]>(
const unboxedArgs = correctedArgs.map(unboxApiResponse);

const fetchedData =
initialPromise === undefined
initialPromise.res === undefined
? await fetchData(...((unboxedArgs || []) as Parameters<typeof fetchData>))
: await initialPromise;
: await initialPromise.res;

setPendingData({
isInProgress: false,
Expand Down

0 comments on commit 08e5dca

Please sign in to comment.