-
Notifications
You must be signed in to change notification settings - Fork 16
React Query Developer's Guide
The React-Query Milestone replaces the tapis-redux
middleware for managing the lifecycle of API requests with the react-query
hooks library. The advantages of using this library are:
- Significantly reduction in boilerplate code, compared to using
react-redux-saga
- Opinionated, hooks-based method for performing results caching, pagination and automated re-fetching
Calls to tapis-typescript
are now wrapped in utility functions contained in the tapis-api
module. The exports of these functions are promises on the results that also provide error json decoding, should a standard TAPIS 4-stanza response be provided on a failed API call. As an example, we will look at the structure of the login
function for the Authenticator
module.
Each TAPIS function wrapper should have a parameter list with no default values. This means that:
- Callers must explicitly provide a value
- If an explicitly provided value will result in an API error, that error should be visible to higher order clients
Each TAPIS function wrapper should be declared to return a Promise of the wrapped function return type. See 'login'. In the case of the login
function, the createToken
function returns the RespCreateToken
type, so the login
wrapper should return a Promise of that type.
To avoid boilerplate and provide consistency between wrappers, the apiGenerator
and errorDecoder
generic utility functions should be used.
apiGenerator
generates an API objects that have an injected module configuration. The call to apiGenerator
requires the tapis-typescript
modules as parameters, including a basePath
tenant URL configuration as well as an optional jwt
token.
errorDecoder
provides promise based handling of any errors generated by tapis-typescript
calls. The call to errorDecoder
should accept an arrow function as its only parameter. The arrow function should return the result of the tapis-typescript
promise function. If the result is a JSON error, it will automatically be decoded.
This module should provide one hook per tapis-typescript
operation, organized by TAPIS service. Each hook will return react-query
hook results that invoke calls to tapis-api
functions. There are three types of react-query
hooks currently in use:
-
useQuery
, for when a limited number of results are returned by an API call -
useInfiniteQuery
, for when a utility function requires pagination of results -
useMutation
, for when a utility function sends a POST-like operation that is not subject to automatic re-querying
tapis-hooks/systems/useList
is an example of a hook that uses useQuery
.
It first makes a call to the useTapisConfig
hook to retrieve the configured TAPIS basePath and current JWT. This follows react-query
Dependent Query pattern, in that if the TAPIS configuration changes (such as a user logging in or logging out) the useQuery
hook will automatically re-run the query.
The call to react-query's useQuery
hook requires a unique set of values as a key to the useQuery
call. This provides react-query
a way of differentiating between cached requests. The three parameters seen here are:
- A string from
queryKeys.ts
in the same namespace - The request parameter object to differentiate between requests to the same endpoint with different parameters
- The
accessToken
associated with the user making the request.
When a client component calls the useList
hook, they can expect that the data returned by the hook will be automatically refreshed (as part of React Query's feature set) and cached to prevent duplicate calls.
The function that makes the request is an arrow function wraps a call to the list
function. This pattern is frequently used to pass parameters to the utility function from within the hook that are derived from other hooks (such as useTapisConfig
).
Lastly, the enabled
option for React-Query allows the hook to prevent unnecessary calls that would fail, such as before authentication occurs.
The useList
hook for the Files service uses React Query's useInfiniteQuery
hook, which provides pagination capabilities. This allows a downstream client such as FilesListing
to use the fetchNextPage
function returned by the hook, thus facilitating pagination.
The arrow function that wraps the listing call is passed the pageParams
object by React-Query whenever a page is fetched. (In this line of code, we provide it a default value generated from the hook parameters for its retrieval of the first page of results.) useInfiniteQuery
requires the getNextPageParam
function to determine how to calculate the next page parameters for TAPIS. The tapisNextPageParam
generic function call is used to prescribe a general method for pagination that works for all TAPIS requests.
The data
object returned by useInfiniteQuery
differs from useQuery
in that the results are part of a page array. For some views, it may be more useful to simply receive all paginated results in one array for UI use cases like infinite scrolling. Therefore, this hook also returns a concatenatedResults
array with all pages reduced into one array. A downstream client such as FilesListing
in tapis-ui
can access this array to treat all results as one aggregated listing.
useMutation
based hooks should be used for operations that will cause an upstream change in state, such as POST and DELETE. The query keys for operations should explicitly include at least a few required parameters of the operation, to distinguish its identity from other calls to the same endpoint. In the case of useSubmit
in the Jobs hooks, appId
and appVersion
are used as keys.
The wrapper function for the submit
utility must accept exactly one parameter. The other parameters for the utility function may be local variables within the hook, such as configuration from useTapisConfig
. Generally speaking, this single parameter should be the request object that is required for the call to @tapis/tapis-typescript
. It should not be a custom object type that is destructured into further parameters.
Some additional hooks such as useEffect
may be used in this hook to provide some state resets to higher order UI components.
The primary mechanism that allows a higher order component to use the mutation is a function that wraps the mutate
hook from React Query. React Query's mechanisms pass a single parameter from mutate
to the arrow function that wraps the API call.
Only a single parameter may be used with the call to mutate
. We could not determine a way to pass an argument list. If you require multiple values to be passed, consider using a destructured object like in the useLogin
hook.