use-query-rd
v2.0.3
Published
A drop in replacement for apollo client's `useQuery` hook with a return type that mimics Elm's RemoteData ADT
Downloads
15
Readme
use-query-rd
A drop in replacement for apollo client's
useQuery
hook with a return type that mimics Elm's RemoteData ADT
Motivation
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.
Inspo
useQueryRd
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>;
};
Pattern Matching
match
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.
Signature
const match: <T, D>(rd: RemoteData<D>, matcher: Matcher<T, D>) => T;
Example
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;
fold
Takes four functions corresponding to the four tags and a RemoteData
value. The function corresponding to the tag of the value is applied.
Signature
const fold: <T, D>(
initialized: () => T,
pending: () => T,
failure: (error: ApolloError) => T,
success: (data: D) => T
) => (_: RemoteData<D>) => T;
Example
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;
Mapping
map
Apply the supplied function to the RemoteData
if tag is Success
, otherwise return original RemoteData
Signature
const map: <T, D>(f: (a: T) => D, fa: RemoteData<T>) => RemoteData<D>;
Example
const myInitialData = useSomeData()._rd;
const formattedData = map((res: MyResultType) => {
const manipulatedData = doSomething(res);
return {
myManipulatedData: manipulatedData,
};
}, myInitialData);
andMap
Put the results of two RemoteData calls together. Used to make mapX.
Signature
const andMap: <RD1, RD2>(
rd1: RemoteData<RD1>,
rd2: RemoteData<(d: RD1) => RD2>
) => RemoteData<RD2>;
map2
Combine two remote data sources with the given function. The result will succeed when (and if) both sources succeed.
Signature
const map2: <D, D2, D3>(
f: (d: D) => (d2: D2) => D3,
rd1: RemoteData<D>,
rd2: RemoteData<D2>
) => RemoteData<D3>;
Example
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)}
</>
),
});
Constructors
initialized
Constructs a new RemoteData
with a tag of Initialized
. This represents a network request yet to be made.
Signature
const initialized: <T = never>() => RemoteData<T>;
pending
Constructs a new RemoteData
with a tag of Pending
. This represents an in flight network request.
Signature
const pending: <T = never>() => RemoteData<T>;
failure
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
.
Signature
const failure: <T = never>(error: ApolloError) => RemoteData<T>;
success
Constructs a new RemoteData
with a tag of Success
. This represents a resolved network requests with a valid response.
Signature
const success: <D = never>(data: D) => RemoteData<D>;
Refinements
isInitialized
Returns true
if the rd is an instance of Initialized
, false
otherwise
Signature
const isInitialized: <D = never>(rd: RemoteData<D>) => rd is Pending;
isLoading
Returns true
if the rd is an instance of Pending
, false
otherwise
Signature
const isLoading: <D = never>(rd: RemoteData<D>) => rd is Pending;
isFailure
Returns true
if the rd is an instance of Failure
, false
otherwise
Signature
const isFailure: <D = never>(rd: RemoteData<D>) => rd is Failure;
isSuccess
Returns true
if the rd is an instance of Success
, false
otherwise
Signature
const isSuccess: <D = never>(rd: RemoteData<D>) => rd is Success<D>;