Skip to content

React Query Developer's Guide

jchuahtacc edited this page Sep 1, 2021 · 15 revisions

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

tapis-api

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.

Wrapper functions should accept explicitly defined parameters

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

Wrapper functions should return a Promise

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.

Wrapper functions should use the apiGenerator and errorDecoder utils

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.

tapis-hooks

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

useQuery-based Hooks

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.

useInfiniteQuery-based hooks

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

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.