Skip to content

Commit

Permalink
Farzin/90439/R&D/Make use of react-query in @deriv/api package (#…
Browse files Browse the repository at this point in the history
…7845)

* feat: ⬆️ update `@deriv/api-types` to `1.0.85`

* feat(api): ✨ update api types with all the requests from `@deriv/api-types`

* feat(api): ✨ add `useAPI` hook

* feat(api): ✨ add `useAPISubscription` hook

* feat(api): ✨ add `useAPISubscription` hook

* feat(api): ✨ improve types

* feat(api): ✨ rename hooks

* feat(api): ✨ rename hooks

* refactor(api): 🔥 remove `QueryClient` config

* refactor(api): 🔥 remove unnecessary utils file

* refactor(api): 🔥 remove unnecessary export types

* test(api): ✅ add test for `useFetch` hook

* refactor(api): 🔥 improve `useSubscription` hook

* feat(api): ✨ use `useMutation` hook inside `useRequest` hook

* test(cashier): ✅ fix the failing test

* refactor(api): ♻️ move request props to `payload` object

* refactor(api): 📝 resolve PR comments

* feat(api): ⬆️ migrating to React Query 4

* fix(cashier): 🐛 fix type errors

* feat(api): ✨ add `useInvalidateQuery` hook

* refactor(api): 📝 resolve PR comments

* ci: 💚 trigger build

* ci: 💚 trigger build

---------

Co-authored-by: Farzin Mirzaie <farzin@deriv.com>
  • Loading branch information
farzin-deriv and Farzin Mirzaie committed Apr 5, 2023
1 parent 64183a8 commit b727dc2
Show file tree
Hide file tree
Showing 27 changed files with 290 additions and 218 deletions.
2 changes: 1 addition & 1 deletion packages/account/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"dependencies": {
"@binary-com/binary-document-uploader": "^2.4.7",
"@deriv/api-types": "^1.0.85",
"@deriv/api-types": "^1.0.94",
"@deriv/components": "^1.0.0",
"@deriv/shared": "^1.0.0",
"@deriv/translations": "^1.0.0",
Expand Down
6 changes: 4 additions & 2 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
"main": "src/index.ts",
"dependencies": {
"@deriv/shared": "^1.0.0",
"react": "^17.0.2"
"react": "^17.0.2",
"@tanstack/react-query": "^4.28.0",
"@tanstack/react-query-devtools": "^4.28.0"
},
"devDependencies": {
"@deriv/api-types": "^1.0.85",
"@deriv/api-types": "^1.0.94",
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^7.0.2",
"@testing-library/user-event": "^13.5.0",
Expand Down
14 changes: 14 additions & 0 deletions packages/api/src/APIProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, { PropsWithChildren } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient();

const APIProvider = ({ children }: PropsWithChildren<unknown>) => (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
);

export default APIProvider;
29 changes: 29 additions & 0 deletions packages/api/src/__tests__/useFetch.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import useFetch from '../useFetch';
import APIProvider from '../APIProvider';
import { TSocketResponse } from '../../types';

jest.mock('@deriv/shared', () => ({
WS: {
send: jest.fn(() =>
Promise.resolve<TSocketResponse<'ping'>>({
msg_type: 'ping',
ping: 'pong',
echo_req: {},
})
),
},
}));

describe('useFetch', () => {
test('should call ping and get pong in response', async () => {
const wrapper = ({ children }: { children: JSX.Element }) => <APIProvider>{children}</APIProvider>;

const { result, waitFor } = renderHook(() => useFetch('ping'), { wrapper });

await waitFor(() => result.current.isSuccess, { timeout: 10000 });

expect(result.current.data).toEqual('pong');
});
});
31 changes: 31 additions & 0 deletions packages/api/src/__tests__/useRequest.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import useRequest from '../useRequest';
import APIProvider from '../APIProvider';
import { TSocketResponse } from '../../types';

jest.mock('@deriv/shared', () => ({
WS: {
send: jest.fn(() =>
Promise.resolve<TSocketResponse<'verify_email'>>({
msg_type: 'verify_email',
verify_email: 1,
echo_req: {},
})
),
},
}));

describe('useRequest', () => {
test('should call verify_email and get 1 in response', async () => {
const wrapper = ({ children }: { children: JSX.Element }) => <APIProvider>{children}</APIProvider>;

const { result, waitFor } = renderHook(() => useRequest('verify_email'), { wrapper });

result.current.mutate([{ payload: { verify_email: 'john@example.com', type: 'request_email' } }]);

await waitFor(() => result.current.isSuccess, { timeout: 10000 });

expect(result.current.data).toEqual(1);
});
});
8 changes: 4 additions & 4 deletions packages/api/src/__tests__/useSubscription.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useWS as useWSShared } from '@deriv/shared';
import { useWS } from '@deriv/shared';
import useSubscription from '../useSubscription';

jest.mock('@deriv/shared');

const mockUseWSShared = useWSShared as jest.MockedFunction<typeof useWSShared>;
const mockUseWS = useWS as jest.MockedFunction<typeof useWS>;

describe('useSubscription', () => {
test('should subscribe to p2p_order_info and get the order updates', async () => {
mockUseWSShared.mockReturnValue({
mockUseWS.mockReturnValue({
subscribe: jest.fn(() => {
return {
subscribe: async (onData: (response: unknown) => void, onError: (response: unknown) => void) => {
Expand Down Expand Up @@ -36,7 +36,7 @@ describe('useSubscription', () => {
expect(result.current.data).toBe(undefined);

act(() => {
result.current.subscribe({ id: '2' });
result.current.subscribe({ payload: { id: '2' } });
});

await waitForNextUpdate();
Expand Down
105 changes: 0 additions & 105 deletions packages/api/src/__tests__/useWS.spec.tsx

This file was deleted.

5 changes: 4 additions & 1 deletion packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { default as useWS } from './useWS';
export { default as APIProvider } from './APIProvider';
export { default as useFetch } from './useFetch';
export { default as useInvalidateQuery } from './useInvalidateQuery';
export { default as useRequest } from './useRequest';
export { default as useSubscription } from './useSubscription';
19 changes: 19 additions & 0 deletions packages/api/src/useFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import { getQueryKeys, send } from './utils';
import type {
TSocketEndpointNames,
TSocketAcceptableProps,
TSocketResponseData,
TSocketRequestCleaned,
TSocketRequestQueryOptions,
} from '../types';

const useFetch = <T extends TSocketEndpointNames>(name: T, ...props: TSocketAcceptableProps<T, true>) => {
const prop = props?.[0];
const payload = prop && 'payload' in prop ? (prop.payload as TSocketRequestCleaned<T>) : undefined;
const options = prop && 'options' in prop ? (prop.options as TSocketRequestQueryOptions<T>) : undefined;

return useQuery<TSocketResponseData<T>, unknown>(getQueryKeys(name, payload), () => send(name, payload), options);
};

export default useFetch;
10 changes: 10 additions & 0 deletions packages/api/src/useInvalidateQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useQueryClient } from '@tanstack/react-query';
import { TSocketEndpointNames } from '../types';

const useInvalidateQuery = () => {
const queryClient = useQueryClient();

return <T extends TSocketEndpointNames>(name: T) => queryClient.invalidateQueries([name]);
};

export default useInvalidateQuery;
20 changes: 20 additions & 0 deletions packages/api/src/useRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useMutation } from '@tanstack/react-query';
import { send } from './utils';
import type {
TSocketEndpointNames,
TSocketAcceptableProps,
TSocketResponseData,
TSocketRequestCleaned,
TSocketRequestMutationOptions,
} from '../types';

// Todo: Get rid of redundant array wrapper for the props argument.
const useRequest = <T extends TSocketEndpointNames>(name: T, options?: TSocketRequestMutationOptions<T>) =>
useMutation<TSocketResponseData<T>, unknown, TSocketAcceptableProps<T>>(props => {
const prop = props?.[0];
const payload = prop && 'payload' in prop ? (prop.payload as TSocketRequestCleaned<T>) : undefined;

return send(name, payload);
}, options);

export default useRequest;
84 changes: 52 additions & 32 deletions packages/api/src/useSubscription.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,62 @@
import { useState } from 'react';
import { useWS as useWSShared } from '@deriv/shared';
import { TSocketSubscribableEndpointNames, TSocketAcceptableProps, TSocketResponseData } from '../types';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useWS } from '@deriv/shared';
import type {
TSocketSubscribableEndpointNames,
TSocketAcceptableProps,
TSocketResponseData,
TSocketRequestCleaned,
} from '../types';

const useSubscription = <T extends TSocketSubscribableEndpointNames>(name: T) => {
const [is_loading, setIsLoading] = useState(false);
const [is_subscribed, setSubscribed] = useState(false);
const [error, setError] = useState<unknown>();
const [data, setData] = useState<TSocketResponseData<T>>();
const [subscriber, setSubscriber] = useState<{ unsubscribe?: VoidFunction }>();
const WS = useWSShared();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onData = (response: any) => {
setData(response[name === 'ticks' ? 'tick' : name]);
setIsLoading(false);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onError = (response: any) => {
setError(response.error);
setIsLoading(false);
};

const subscribe = (...props: TSocketAcceptableProps<T>) => {
setIsLoading(true);
setSubscribed(true);

try {
setSubscriber(WS.subscribe({ [name]: 1, subscribe: 1, ...(props[0] || {}) }).subscribe(onData, onError));
} catch (e) {
setError(e);
}
};

const unsubscribe = () => {
subscriber?.unsubscribe?.();
const subscriber = useRef<{ unsubscribe?: VoidFunction }>();
const WS = useWS();

const subscribe = useCallback(
(...props: TSocketAcceptableProps<T>) => {
const prop = props?.[0];
const payload = prop && 'payload' in prop ? (prop.payload as TSocketRequestCleaned<T>) : undefined;

setIsLoading(true);
setSubscribed(true);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onData = (response: any) => {
setData(response[name === 'ticks' ? 'tick' : name]);
setIsLoading(false);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onError = (response: any) => {
setError(response.error);
setIsLoading(false);
};

try {
subscriber.current = WS.subscribe({ [name]: 1, subscribe: 1, ...(payload || {}) }).subscribe(
onData,
onError
);
} catch (e) {
setError(e);
}
},
[WS, name]
);

const unsubscribe = useCallback(() => {
subscriber.current?.unsubscribe?.();
setSubscribed(false);
};
}, []);

useEffect(() => {
return () => {
unsubscribe();
};
}, [unsubscribe]);

return { subscribe, unsubscribe, is_loading, is_subscribed, error, data };
};
Expand Down
Loading

1 comment on commit b727dc2

@vercel
Copy link

@vercel vercel bot commented on b727dc2 Apr 5, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

deriv-app – ./

binary.sx
deriv-app.vercel.app
deriv-app-git-master.binary.sx
deriv-app.binary.sx

Please sign in to comment.