-
Notifications
You must be signed in to change notification settings - Fork 0
Experiment: TanStack Query #39
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
base: develop
Are you sure you want to change the base?
Conversation
b08dad6 to
af15f6b
Compare
| - `localization/` - Things related to user locale | ||
| - `navigation/` - Navigators, routes | ||
| - `redux/` - Actions, reducers, sagas | ||
| - `remote/` - Remote state (via TanStack Query) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This indicates remote state (server state) in terms of reflecting inside the app what's stored on some remote server (backend). It's keeping networking state - loading, errors and data from requests and responses.
It's different than redux/, which is client local state, that doesn't need to by synchronised with server. It's updated by local user's action or some effects from remote state updates.
This structure allows to keep them separate.
| } | ||
|
|
||
| const DemoScreen = ({ navigation }: DemoScreenProps) => { | ||
| const [isLogoutLoading, setIsLogoutLoading] = useState(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loading state is handled by TanStack query, see useLatestComicQuery. We don't have to implement our own state here. State of query will be the same (cached) if we call query with the same id from another component.
|
|
||
| useEffect(() => { | ||
| if (isNotRequested(comicRequest)) { | ||
| dispatch(getLatestComicAsync()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to explicitly call initial fetch. It's handled by default by TanStack Query inside useLatestComicQuery
| persistor.pause() | ||
| await clearPersistence() | ||
| dispatch(resetStore()) | ||
| persistor.persist() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleanup logic moved to useLogOutMutation, which is in template/src/remote/auth.ts.
Also, no need to set loading states, because it's handled internally by lib.
| }, | ||
| getLatestComicAsyncFailure: (state, action: PayloadAction<Error['message']>) => { | ||
| state.comic = Failure(action.payload) | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This boilerplate of 3 actions for calling request and receiving success/failure was replaced by useQuery.
|
|
||
| interface DemoState { | ||
| counter: number | ||
| comic: RemoteData<Comic, Error['message']> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to store RemoteData values (errors, loading) in our local state, because it's stored internally by useQuery in it's state that represents server state. This way networking doesn't mix with local state (MST or redux).
| } | ||
|
|
||
| export function* watchGetLatestComicSaga() { | ||
| yield* takeEvery(getLatestComicAsync, fetchLatestComic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sagas logic was replaced by useQuery in useLatestComicQuery
| @@ -1,87 +0,0 @@ | |||
| export enum RemoteDataStates { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole logic from api.ts file, with RemoteData, was replaced by useQuery and useMutation. This logic is in TanStack Query library.
| expect(result.counter).toEqual(410) | ||
| }) | ||
|
|
||
| it('set getLatestComicAsync to Loading if there is no comic in store', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is removed, because it's testing switching to Loading state when request is started. It's handled by lib now, which is tested separately (in lib).
see isLoading for query: https://tanstack.com/query/v5/docs/react/reference/useQuery
or isPending for mutation: https://tanstack.com/query/v5/docs/react/reference/useMutation
| expect(result.comic).toEqual(Loading) | ||
| }) | ||
|
|
||
| it('set getLatestComicAsync to Refreshing if there is already comic in store', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is removed, because it's testing switching to Refreshing state when request is started while already having data. It's handled by lib now, which is tested separately (in lib).
See isRefetching -> https://tanstack.com/query/v5/docs/react/reference/useQuery
| expect(result.comic).toEqual(Refreshing(comicMockParsed)) | ||
| }) | ||
|
|
||
| it('set getLatestComicAsyncSuccess state', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is removed, because it's testing switching to Success state when received successful response. It's handled by lib now, which is tested separately (in lib).
See isSuccess in https://tanstack.com/query/v5/docs/react/reference/useQuery
| expect(result.comic).toEqual(Success(comicMockResponse)) | ||
| }) | ||
|
|
||
| it('set getLatestComicAsyncFailure state', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is removed, because it's testing switching to Failure state when received successful response. It's handled by lib now, which is tested separately (in lib).
See isError in https://tanstack.com/query/v5/docs/react/reference/useQuery
| import { decrementCounterBy, demoSlice, incrementCounterBy } from '@screens/demoSlice' | ||
|
|
||
| describe('DemoSlice', () => { | ||
| describe('Demo saga fetchLatestComic', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests of fetchLatestComic are replaced by tests of useLatestComicQuery
| } as ReturnType<typeof remoteComics.useLatestComicQuery>) | ||
|
|
||
| jest.mock('@remote/auth', () => ({ | ||
| useLogOutMutation: jest.fn().mockImplementation(() => ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hooks with effects are mocked in component unit tests to avoid unfinished tests (promises running indefinitely).
It's a separate issue that existed here before migration to TanStack Query.
| import { mapComic } from '@api/mappers/comicMappers' | ||
|
|
||
| export const useLatestComicQuery = () => { | ||
| return useQuery({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This useQuery replaced a lot of boilerplate for fetching the latest comic
|
|
||
| export const useLatestComicQuery = () => { | ||
| return useQuery({ | ||
| queryKey: ['comic', 'latest'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This key - ['comic', 'latest'] - can be used in another place to get to the same data (cached data, error and loading states)
| payload: { | ||
| tokens: { | ||
| data: fakeAuthTokens, | ||
| type: RemoteDataStates.SUCCESS, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to store and test request state, because it's implemented and tested in useQuery and useMutation
| describe('#logInAsyncFailure', () => { | ||
| it('should store auth error message', () => { | ||
| const state = authSlice.reducer(initialState, logInAsyncFailure(logInErrorMessage)) | ||
| expect(state.tokens).toEqual(Failure(logInErrorMessage)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed those tests because there is no need to test switching states: Loading, Success and Failure, because it's handled by useQuery/useMutation in library
| }) | ||
| }) | ||
|
|
||
| describe('#watchLogInSaga', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests of logging in logic are moved to tests of useLogInMutation
Description
It's purpose is a comparison of approaches to networking layer: current one in the project vs using query library.
I left some comments in this PR to help understand those changes.
Changes
Migrated our remote data layer to TanStack Query. Template in this experiment uses
useQueryanduseMutation. Basically it removed all the boilerplate implemented intemplate/src/utils/api.ts.Considerations
TODO:
useLatestComicQuery,useLogInMutation,useLogOutMutation,apisauce(it's related to axios) to also remove our boilerplate fromtemplate/src/api/common.ts.Demo
No demo. It looks the same. 🌝