Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui-core]: add onSuccess and onError callback for useLoadData hook #3849

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 58 additions & 7 deletions desktop/core/src/desktop/js/utils/hooks/useLoadData.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('useLoadData', () => {
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(true);

waitFor(() => {
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, mockOptions.params, expect.any(Object));
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeUndefined();
Expand All @@ -84,7 +84,7 @@ describe('useLoadData', () => {
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(true);

waitFor(() => {
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, undefined, expect.any(Object));
expect(result.current.data).toBeUndefined();
expect(result.current.error).toEqual(mockError);
Expand All @@ -108,7 +108,7 @@ describe('useLoadData', () => {
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(true);

waitFor(() => {
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, undefined, expect.any(Object));
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeUndefined();
Expand All @@ -121,7 +121,7 @@ describe('useLoadData', () => {
result.current.reloadData();
});

waitFor(() => {
await waitFor(() => {
expect(mockGet).toHaveBeenCalledTimes(2);
expect(mockGet).toHaveBeenCalledWith(mockUrl, undefined, expect.any(Object));
expect(result.current.data).toEqual({ ...mockData, product: 'Hue 2' });
Expand All @@ -137,7 +137,7 @@ describe('useLoadData', () => {
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(true);

waitFor(() => {
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, undefined, expect.any(Object));
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeUndefined();
Expand All @@ -157,7 +157,7 @@ describe('useLoadData', () => {
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(true);

waitFor(() => {
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, mockOptions.params, expect.any(Object));
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeUndefined();
Expand All @@ -172,11 +172,62 @@ describe('useLoadData', () => {

rerender({ url: mockUrl, options: newOptions });

waitFor(() => {
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, newOptions.params, expect.any(Object));
expect(result.current.data).toEqual(newMockData);
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(false);
});
});

it('should call onSuccess callback', async () => {
const mockOnSuccess = jest.fn();
const mockOnError = jest.fn();
const { result } = renderHook(() =>
useLoadData(mockUrl, {
onSuccess: mockOnSuccess,
onError: mockOnError
})
);

expect(result.current.data).toBeUndefined();
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(true);

await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, undefined, expect.any(Object));
expect(result.current.data).toEqual(mockData);
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(false);
expect(mockOnSuccess).toHaveBeenCalledWith(mockData);
expect(mockOnError).not.toHaveBeenCalled();
});
});

it('should call onError callback', async () => {
const mockError = new Error('Fetch error');
mockGet.mockRejectedValue(mockError);

const mockOnSuccess = jest.fn();
const mockOnError = jest.fn();
const { result } = renderHook(() =>
useLoadData(mockUrl, {
onSuccess: mockOnSuccess,
onError: mockOnError
})
);

expect(result.current.data).toBeUndefined();
expect(result.current.error).toBeUndefined();
expect(result.current.loading).toBe(true);

await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith(mockUrl, undefined, expect.any(Object));
expect(result.current.data).toBeUndefined();
expect(result.current.error).toEqual(mockError);
expect(result.current.loading).toBe(false);
expect(mockOnSuccess).not.toHaveBeenCalled();
expect(mockOnError).toHaveBeenCalledWith(mockError);
});
});
});
10 changes: 10 additions & 0 deletions desktop/core/src/desktop/js/utils/hooks/useLoadData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type IOptions<T, U> = {
params?: U;
fetchOptions?: ApiFetchOptions<T>;
skip?: boolean;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
};

type IUseLoadData<T> = {
ramprasadagarwal marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -61,8 +63,16 @@ const useLoadData = <T, U = unknown>(url?: string, options?: IOptions<T, U>): IU
const fetchUrl = localOptions?.urlPrefix ? `${localOptions.urlPrefix}${url}` : url;
const response = await get<T, U>(fetchUrl, localOptions?.params, fetchOptions);
setData(response);
setError(undefined);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setError not required as it is set above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is required in the edge case.

  1. First call made which resulted in error. Hence error is set to non-null and data is unchanged.
  2. Second call is made either by triggering refetch function or by changing and of the options. When its a success, the old error state is still set to non-null and data after second call is also set to non-null.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I over-sighted the setError at line 63.
Thanks for flagging. I have removed it

if (localOptions?.onSuccess) {
localOptions.onSuccess(response);
}
} catch (error) {
setError(error instanceof Error ? error : new Error(error));
setData(undefined);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old data will be removed do we want that?
lets say there is table already loaded and we are calling useLoadData hook in a interval. in case of api fails, we can throw error but should we remove the tables data?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this makes sense.

if (localOptions?.onError) {
localOptions.onError(error);
}
} finally {
setLoading(false);
}
Expand Down