UNPKG

use-query-rd

Version:

A drop in replacement for apollo client's `useQuery` hook with a return type that mimics Elm's RemoteData ADT

304 lines (218 loc) 8.2 kB
# `use-query-rd` [![NPM](https://img.shields.io/npm/v/use-query-rd?style=for-the-badge)](https://www.npmjs.com/package/use-query-rd) > A drop in replacement for apollo client's `useQuery` hook with a return type that mimics Elm's RemoteData ADT - [`use-query-rd`](#use-query-rd-) - [Motivation](#motivation) - [Inspo](#inspo) - [useQueryRd](#usequeryrd) - [Pattern Matching](#pattern-matching) - [match](#match) - [Signature](#signature) - [Example](#example) - [fold](#fold) - [Signature](#signature-1) - [Example](#example-1) - [Mapping](#mapping) - [map](#map) - [Signature](#signature-2) - [Example](#example-2) - [andMap](#andmap) - [Signature](#signature-3) - [map2](#map2) - [Signature](#signature-4) - [Example](#example-3) - [Constructors](#constructors) - [initialized](#initialized) - [Signature](#signature-5) - [pending](#pending) - [Signature](#signature-6) - [failure](#failure) - [Signature](#signature-7) - [success](#success) - [Signature](#signature-8) - [Refinements](#refinements) - [isInitialized](#isinitialized) - [Signature](#signature-9) - [isLoading](#isloading) - [Signature](#signature-10) - [isFailure](#isfailure) - [Signature](#signature-11) - [isSuccess](#issuccess) - [Signature](#signature-12) ## 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 - [RemoteData for Elm](https://package.elm-lang.org/packages/krisajenkins/remotedata/latest/) - [Make Impossible States Impossible](https://kentcdodds.com/blog/make-impossible-states-impossible) ## 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 ```typescript function useQuery<TData = any, TVariables = OperationVariables>( query: DocumentNode | TypedDocumentNode<TData, TVariables>, options?: QueryHookOptions<TData, TVariables> ): QueryResult<TData, TVariables>; ``` > useQueryRd ```typescript 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 ```typescript const match: <T, D>(rd: RemoteData<D>, matcher: Matcher<T, D>) => T; ``` #### Example ```typescript 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 ```typescript const fold: <T, D>( initialized: () => T, pending: () => T, failure: (error: ApolloError) => T, success: (data: D) => T ) => (_: RemoteData<D>) => T; ``` #### Example ```typescript 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 ```typescript const map: <T, D>(f: (a: T) => D, fa: RemoteData<T>) => RemoteData<D>; ``` #### Example ```typescript 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. [@see](https://github.com/krisajenkins/remotedata/blob/6.0.1/src/RemoteData.elm#L361) #### Signature ```typescript 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 ```typescript const map2: <D, D2, D3>( f: (d: D) => (d2: D2) => D3, rd1: RemoteData<D>, rd2: RemoteData<D2> ) => RemoteData<D3>; ``` #### Example ```typescript 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 ```typescript const initialized: <T = never>() => RemoteData<T>; ``` ### pending Constructs a new `RemoteData` with a tag of `Pending`. This represents an in flight network request. #### Signature ```typescript 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 ```typescript 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 ```typescript const success: <D = never>(data: D) => RemoteData<D>; ``` ## Refinements ### isInitialized Returns `true` if the rd is an instance of `Initialized`, `false` otherwise #### Signature ```typescript const isInitialized: <D = never>(rd: RemoteData<D>) => rd is Pending; ``` ### isLoading Returns `true` if the rd is an instance of `Pending`, `false` otherwise #### Signature ```typescript const isLoading: <D = never>(rd: RemoteData<D>) => rd is Pending; ``` ### isFailure Returns `true` if the rd is an instance of `Failure`, `false` otherwise #### Signature ```typescript const isFailure: <D = never>(rd: RemoteData<D>) => rd is Failure; ``` ### isSuccess Returns `true` if the rd is an instance of `Success`, `false` otherwise #### Signature ```typescript const isSuccess: <D = never>(rd: RemoteData<D>) => rd is Success<D>; ```