permalinkuseRemoteData
With this Hook you can load data from a remote source and display it in your component.
RemoteData
is cool because it makes loading states explicit and easy to handle.
You can show a skeleton while loading, an error message if something went wrong or the data if it was loaded successfully.
src/Hooks/UseRemoteData.purs
module Hooks.UseRemoteData
( UseRemoteData(..)
, UseRemoteDataDispatch(..)
, useRemoteData
, useRemoteDataDispatch
, useRemoteDataDispatchWithDefault
, useRemoteDataWithDefault
, Loadable
) where
import Prelude hiding (top)
import Data.Either (Either, either)
import Data.Maybe (Maybe(..), maybe)
import Data.Newtype (class Newtype)
import Data.Time.Duration (Milliseconds(Milliseconds), Seconds(Seconds), fromDuration)
import Data.Traversable (for_)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Aff (Aff, Fiber, error, finally, forkAff, killFiber, launchAff_)
import Effect.Class (liftEffect)
import Network.RemoteData (RemoteData)
import React.Basic.Hooks (Hook, UseEffect, UseRef, UseState, coerceHook, readRef, useEffect, writeRef)
import Effect.Aff (delay) as Aff
import Network.RemoteData as RD
import React.Basic.Hooks as React
import Effect.Class.Console (log) as Console
type Loadable i err o =
{ data ∷ RemoteData err o
, load ∷ i → Effect Unit
, reset ∷ Effect Unit
}
newtype UseRemoteData ∷ ∀ k. k → Type → Type → Type → Type
newtype UseRemoteData i err o hooks = UseRemoteData
( UseRef (Maybe (Fiber Unit))
( UseState (RemoteData err o)
hooks
)
)
derive instance Newtype (UseRemoteData i o err hooks) _
useRemoteDataWithDefault
∷ ∀ i o err
. Maybe o
→ (i → Aff (Either err o))
→ Hook (UseRemoteData i err o) (Loadable i err o)
useRemoteDataWithDefault startValue loadFn =
coerceHook React.do
let initialValue = maybe RD.NotAsked RD.Success startValue
value /\ setValue ← React.useState' initialValue
fiberRef ← React.useRef Nothing
let
load input =
launchAff_ do
cancel
fib ← do
forkAff do
loadingFib <- forkAff do
when (RD.isSuccess value) do
-- Keep the old data for a while to avoid flickering
Aff.delay (500.0 # Milliseconds)
setValue RD.Loading # liftEffect
finally (writeRef fiberRef Nothing # liftEffect) do
result ∷ Either err o ← loadFn input
killFiber (error "Cancelled") loadingFib
setValue (either RD.Failure RD.Success result) # liftEffect
writeRef fiberRef (Just fib) # liftEffect
cancel = do
fiberOrNot ← readRef fiberRef # liftEffect
for_ fiberOrNot (killFiber (error "Cancelled"))
reset =
launchAff_ do
cancel
setValue RD.NotAsked # liftEffect
pure
{ data: value
, load
, reset
}
useRemoteData
∷ ∀ i o err
. (i → Aff (Either err o))
→ Hook (UseRemoteData i err o) (Loadable i err o)
useRemoteData = useRemoteDataWithDefault Nothing
newtype UseRemoteDataDispatch ∷ ∀ k. k → Type → Type → Type → Type
newtype UseRemoteDataDispatch i err o hooks = UseRemoteDataDispatch
( UseEffect (RemoteData err o)
( UseState (Maybe (Fiber Unit))
( UseState (RemoteData err o)
hooks
)
)
)
derive instance Newtype (UseRemoteDataDispatch i o err hooks) _
useRemoteDataDispatchWithDefault
∷ ∀ i o err
. Eq o
⇒ Eq err
⇒ Maybe o
→ ((RemoteData err o) → Effect Unit)
→ (i → Aff (Either err o))
→ Hook (UseRemoteDataDispatch i err o)
( { load ∷ i → Effect Unit
, cancel ∷ Effect Unit
}
)
useRemoteDataDispatchWithDefault startValue dispatch loadFn =
coerceHook React.do
let initialValue = maybe RD.NotAsked RD.Success startValue
value /\ setValue ← React.useState' initialValue
fiber /\ setFiber ← React.useState' Nothing
useEffect value do
dispatch value
mempty
let
load input =
launchAff_ do
cancel
setValue RD.Loading # liftEffect
fib ←
forkAff
$ finally (setFiber Nothing # liftEffect) do
result ∷ Either err o ← loadFn input
setValue (either RD.Failure RD.Success result) # liftEffect
setFiber (Just fib) # liftEffect
cancel = for_ fiber (killFiber (error "Cancelled"))
reset =
launchAff_ do
cancel
setValue RD.NotAsked # liftEffect
pure
{ load
, cancel: reset
}
useRemoteDataDispatch
∷ ∀ i o err
. Eq o
⇒ Eq err
⇒ ((RemoteData err o) → Effect Unit)
→ (i → Aff (Either err o))
→ Hook (UseRemoteDataDispatch i err o)
( { load ∷ i → Effect Unit
, cancel ∷ Effect Unit
}
)
useRemoteDataDispatch = useRemoteDataDispatchWithDefault Nothing