0.1.1 • Published 4 years ago

@throned/react-resource-ts v0.1.1

Weekly downloads
158
License
MIT
Repository
github
Last release
4 years ago

throned ▲ react-resource-ts

Install

npm install --save @throned/react-resource-ts @throned/resource-ts

yarn add @throned/react-resource-ts @throned/resource-ts

@throned/react-resource-ts has @throned/resource-ts in dependencies, so it will be installed anyway.

Be aware that useResource hook uses AbortController so you may want to add a polyfill.

Intro

React Resource provides hooks and components for Resource ADT.

useResource hook gives you the ability to wrap your asynchronous operation into Resource data type and render each possible state of resource (Initial, Loading, Success, Failure components) as well as define your own matchers (Match).

import * as React from 'react'
import {isLoading} from '@throned/resource-ts'
import {getStateComponents, useResource} from '@throned/react-resource-ts'

type Dog = {message: string}

const loadDog = async (): Promise<Dog> => {
  return fetch('https://dog.ceo/api/breeds/image/random').then(res => {
    if (!res.ok) {
      throw new Error(res.statusText)
    }
    return res.json()
  })
}

const App = () => {
  const dog = useResource(loadDog, {defer: true})
  const State = getStateComponents(dog.resource)
  return (
    <React.Fragment>
      <State.Match states={['initial', 'loading']}>
        <button onClick={dog.run} disabled={isLoading(dog.resource)}>
          Show me doggo!
        </button>
        <State.Loading>
          <p>Loading...</p>
        </State.Loading>
      </State.Match>
      <State.Failure render={(error: Error) => <p>Oops! {error.message}</p>} />
      <State.Success>
        {({message}) => (
          <div>
            <h1>Nice</h1>
            <img src={message} alt="dog" />
            <button onClick={dog.run}>Show me more!</button>
          </div>
        )}
      </State.Success>
    </React.Fragment>
  )
}

API

useResource

const {cancel, reset, resource, run} = useResource<Params, Data, Error>(
  load,
  props,
)

load

(params: Params, config?: Config) => Promise<Data>

Function that returns a promise and may fail with Error type (Error type that provided is useResource type parameters). Accepts optional second parameter of type Config. Config is { signal: AbortController.signal } that you can pass into your fetch to enable cancellation.

props

type UseResourceProps<Params, Data, Error> = {
  cancellable?: boolean
  chain?: Resource<Params, unknown>
  defer?: boolean
  dependencies?: any[]
  onFailure?: (error: Error) => void
  onSuccess?: (data: Data) => void
  params?: Params
  skipLoadingFor?: number
}
cancellable

boolean = true

If true will use Abort Controller abort() to cancel previous load on subsequent load call.

chain

Resource<Params, unknown>

If chain is passed its resource.value will be used instead of params prop when load is executed by useResource. You can use chain to wait until another resource is in success state to get params from it. When chain is provided load function will be executed only when resource in chain is in success.

import {useResource, Success} from '@throned/react-resource-ts'
import {map} from '@throned/resource-ts'

type UserMovie = {movieId: string}

const getUserMovie = () => Promise.resolve({movieId: '1234'})
const getMovie = (id: string) => Promise.resolve({title: 'The Departed'})

const Component = () => {
  const user = useResource(getUserMovie)
  const movie = useResource(getMovie, {
    dependencies: [user.resource],
    chain: map(({movieId}: UserMovie) => movieId)(user.resource),
  })
  return (
    <Success of={movie.resource}>{movie => <h1>{movie.title}</h1>}</Success>
  )
}
defer

boolean = false

If you pass defer: true then load function won't be called automatically. To run load manually you have to call run.

dependencies

any[] = []

Works the same way as dependencies array in useEffect hook. If value of dependencies prop is changed between render useResource will execute load function. Be aware that if defer: true then load won't be executed.

onFailure

(error: Error) => void

Callback that is called when load rejects.

onSuccess

(data: Data) => void

Callback that is called when load resolves.

params

Params

These params will be provided as a first argument of load function. You can overwrite them when calling run function by passing new params to it. Will be ignored by automatic load execution if chain is provided.

skipLoadingFor

number = 150

Number of milliseconds that useResource will wait until changing state to loading. Pass skipLoadingFor: 0 if you want to see loading state immediatly after executing load function.

Returns

cancel

() => void

Cancels last running load function by calling abort() on Abort Controller signal passed within Config. Be aware that it will work only if you handle signal manually in your load, e.g. passing it into your fetch call.

reset

() => void

Cancles last running load function and sets resource into initial state.

resource

Resource<Data, Error>

Holds the state of load execution. To see what you can do with resource check @throned/resource-ts

run

(runParams?: Params = props.params) => Promise<Resource<Data, Error>>

Call run to execute load manually. By default it cancels previous load and uses params passed into props. You can disable auto-cancelling by passing cancellable: false into props and you can overwrite default params by providing runParams.

const {run} = useResource(load, {params: 42})
run() // load(42)
run(100) // load(100)

bindError

<BindedError>() => useResource<Params, Data, Error = BindedError>

Binds the Error type of useResource. It's handy in cases where you want to infer Params and Data from load function, but want to provide Error type explicitly.

class HttpError extends Error {
  /* */
}

const load = (): Promise<{username: string}> => {
  /* */
}

const useHttp = bindError<HttpError>()

const {resource} = useHttp(load)
resource // Resource<{ username: string }, HttpError>

createResource

(load: Load<Params, Data>) => (props: UseResourceProps) => useResource

Binds load function. Use it for creating reusable hook with the same load function.

createResourceWithError

<BindedError>() => (load: Load<Params, Data>) => (props: UseResourceProps) => useResource<Params, Data, Error = BindedError>

Binds error and load function. Use it for creating reusable hook with the same load function and explicit Error type.

Components

Use components to render the UI depending on Resource state.

You can provide of prop explicitly for each component.

const user = useResource(loadUser)
<div>
  <Match of={user.resource} states={['initial', 'loading']}>
    <p>Not ready yet</p>
  </Match>
  <Failure of={user.resource}>{error => /* */}</Failure>
  <Success of={user.resource}>{user => /* */}</Success>
</div>

But recommended way is to use getStateComponents to bind of prop.

const user = useResource(loadUser)
const State = getStateComponents(user.resource)
<div>
  <State.Match states={['initial', 'loading']}>
    <p>Not ready yet</p>
  </State.Match>
  <State.Failure>{error => /* */}</State.Failure>
  <State.Success>{user => /* */}</State.Success>
</div>

All components accepts render props and children (valid jsx or render function). If you provide both children and render function - render will take precedence.

Initial

type InitialProps = {
  children?: ReactNode | (() => ReactNode);
  of: AnyResource;
  render?: () => ReactNode;
}

Loading

type LoadingProps = {
  children?: ReactNode | (() => ReactNode);
  of: AnyResource;
  render?: () => ReactNode;
}

Success

type SuccessProps<D> = {
  children?: ReactNode | ((value: D) => ReactNode);
  of: Resource<D, unknown>;
  render?: (value: D) => ReactNode;
}

Failure

type FailureProps<E> = {
  children?: ReactNode | ((error: E) => ReactNode);
  of: Resource<unknown, E>;
  render?: (error: E) => ReactNode;
}

Match

type MatchProps<D, E> = {
  children?: ReactNode | ((resource: Resource<D, E>) => ReactNode);
  of: Resource<D, E>;
  render?: (resource: Resource<D, E>) => ReactNode;
  states: NotEmptyArray<Tag>;
}

getStateComponents

(of: Resource<D, E>) => { Initial, Loading, Success, Failure, Match }

Returns state components with binded of prop.

Guides

Todo.