A drop in replacement for apollo client's
useQuery
hook with a return type that mimics Elm's RemoteData ADT
Tagging network bound data with the state of the request makes impossible states impossible. This is highly desirable as data requests and the subsequent handling of fetched data is a common cause of logic and runtime errors in apps.
This is a @apollo/client
specific implementation of RemoteData. useQueryRd
is a wrapper around useQuery
that returns one additional property, _rd
. _rd
is a RemoteData
that is generic across Success
. Failure is not generic as it will always be ApolloError
.
useQuery
function useQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: QueryHookOptions<TData, TVariables>
): QueryResult<TData, TVariables>;
useQueryRd
const useQueryRd: <TData, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: QueryHookOptions<TData, TVariables> | undefined
) => QueryResult<TData, TVariables> & {
_rd: RemoteData<TData>;
};
Takes a matcher and a RemoteData
value. The matcher attributes the RemoteData value to a case and applies the matched function. match
support partial matching by supplying a default tag of _
. Any RemoteData states no supplied subsequent to the _
will fallback to the function supplied at _
. A common use case for this is combining the functions for Initialized
and Pending
into one "loading" case.
const match: <T, D>(rd: RemoteData<D>, matcher: Matcher<T, D>) => T;
import React from "react";
import { useQueryRd, match } from "use-query-rd";
const ContainerComponent = (): JSX.Element =>
match(useQueryRd<{ data: MyDataType[] }>(GET_DATA_QUERY)._rd, {
_: <Skeleton />,
Failure: (error) => <MyErrorScreen error={err.error} />,
Success: (data: MyDataType) => <MySuccessScreen data={data.data.myData} />,
});
export default ContainerComponent;
Takes four functions corresponding to the four tags and a RemoteData
value. The function corresponding to the tag of the value is applied.
const fold: <T, D>(
initialized: () => T,
pending: () => T,
failure: (error: ApolloError) => T,
success: (data: D) => T
) => (_: RemoteData<D>) => T;
import React from "react";
import { useQueryRd, fold } from "use-query-rd";
const ContainerComponent = (): JSX.Element =>
fold(
() => <Skeleton />,
() => <Skeleton />,
(error) => <MyErrorScreen error={err.error} />,
(data: MyDataType) => <MySuccessScreen data={data.data.myData} />
)(useQueryRd<{ data: MyDataType[] }>(GET_DATA_QUERY)._rd);
export default ContainerComponent;
Apply the supplied function to the RemoteData
if tag is Success
, otherwise return original RemoteData
const map: <T, D>(f: (a: T) => D, fa: RemoteData<T>) => RemoteData<D>;
const myInitialData = useSomeData()._rd;
const formattedData = map((res: MyResultType) => {
const manipulatedData = doSomething(res);
return {
myManipulatedData: manipulatedData,
};
}, myInitialData);
Put the results of two RemoteData calls together. Used to make mapX.
const andMap: <RD1, RD2>(
rd1: RemoteData<RD1>,
rd2: RemoteData<(d: RD1) => RD2>
) => RemoteData<RD2>;
Combine two remote data sources with the given function. The result will succeed when (and if) both sources succeed.
const map2: <D, D2, D3>(
f: (d: D) => (d2: D2) => D3,
rd1: RemoteData<D>,
rd2: RemoteData<D2>
) => RemoteData<D3>;
export const Map2Example = () => {
const RD1 = useQueryRd<{ launchesPast: Launch[] }>(ROCKETS_QUERY, {
variables: { limit: 100 },
})._rd;
const RD2 = useQueryRd<{ launchpads: Launchpad[] }>(LAUNCHPADS_QUERY)._rd;
const comb =
(rd1: { launchesPast: Launch[] }) => (rd2: { launchpads: Launchpad[] }) => {
return {
one: rd1,
two: rd2,
};
};
return match(map2(comb, RD1, RD2), {
_: () => <p>Loading...</p>,
Failure: (error) => <p>Error while fetching data ({error.message})</p>,
Success: (data) => (
<>
<p>map2</p>
{JSON.stringify(data)}
</>
),
});
Constructs a new RemoteData
with a tag of Initialized
. This represents a network request yet to be made.
const initialized: <T = never>() => RemoteData<T>;
Constructs a new RemoteData
with a tag of Pending
. This represents an in flight network request.
const pending: <T = never>() => RemoteData<T>;
Constructs a new RemoteData
with a tag of Failure
and an ApolloError
. While Failure
is usually generic in _RemoteData_
, useQuery
from @apollo/client
represents all network failures as ApolloError
. Thus, Failure
is strictly typed for ApolloError
.
const failure: <T = never>(error: ApolloError) => RemoteData<T>;
Constructs a new RemoteData
with a tag of Success
. This represents a resolved network requests with a valid response.
const success: <D = never>(data: D) => RemoteData<D>;
Returns true
if the rd is an instance of Initialized
, false
otherwise
const isInitialized: <D = never>(rd: RemoteData<D>) => rd is Pending;
Returns true
if the rd is an instance of Pending
, false
otherwise
const isLoading: <D = never>(rd: RemoteData<D>) => rd is Pending;
Returns true
if the rd is an instance of Failure
, false
otherwise
const isFailure: <D = never>(rd: RemoteData<D>) => rd is Failure;
Returns true
if the rd is an instance of Success
, false
otherwise
const isSuccess: <D = never>(rd: RemoteData<D>) => rd is Success<D>;