Skip to content

Commit

Permalink
Add useAffReducer
Browse files Browse the repository at this point in the history
  • Loading branch information
megamaddu committed Apr 12, 2020
1 parent f7addd5 commit d18d31b
Showing 1 changed file with 65 additions and 1 deletion.
66 changes: 65 additions & 1 deletion src/React/Basic/Hooks/Aff.purs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
module React.Basic.Hooks.Aff
( useAff
, UseAff
, useAffReducer
, AffReducer
, noEffects
, UseAffReducer
) where

import Prelude
import Data.Either (Either(..))
import Data.Foldable (for_)
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype)
import Effect (Effect)
import Effect.Aff (Aff, Error, error, killFiber, launchAff, launchAff_, throwError, try)
import Effect.Class (liftEffect)
import React.Basic.Hooks (Hook, UseEffect, UseState, coerceHook, unsafeRenderEffect, useEffect, useState, (/\))
import React.Basic.Hooks (type (/\), Hook, UnsafeReference(..), UseEffect, UseReducer, UseState, coerceHook, unsafeRenderEffect, useEffect, useReducer, useState, (/\))
import React.Basic.Hooks as React

-- | `useAff` is used for asynchronous effects or `Aff`. The asynchronous effect
Expand Down Expand Up @@ -48,3 +54,61 @@ newtype UseAff deps a hooks
= UseAff (UseEffect deps (UseState (Maybe (Either Error a)) hooks))

derive instance ntUseAff :: Newtype (UseAff deps a hooks) _

-- | Provide an initial state and a reducer function. This is a more powerful
-- | version of `useReducer`, where a state change can additionally queue
-- | asynchronous operations. The results of those operations must be mapped
-- | into the reducer's `action` type. This is essentially the Elm architecture.
-- |
-- | Generally, I recommend `useAff` paired with tools like `useResetToken` over
-- | `useAffReducer` as there are many ways `useAffReducer` can result in race
-- | conditions. `useAff` with proper dependency management will handle previous
-- | request cancellation and ensure your `Aff` result is always in sync with
-- | the provided `deps`, for example. To accomplish the same thing with
-- | `useAffReducer` would require tracking `Fiber`s manually in your state
-- | somehow.. :c
-- |
-- | That said, `useAffReducer` can still be helpful when converting from the
-- | current `React.Basic` (non-hooks) API or for those used to Elm.
-- |
-- | *Note: Aff failures are thrown. If you need to capture an error state, be
-- | sure to capture it in your action type!*
useAffReducer ::
forall state action.
state ->
AffReducer state action ->
Hook (UseAffReducer state action) (state /\ (action -> Effect Unit))
useAffReducer initialState reducer =
coerceHook React.do
{ state, effects } /\ dispatch <-
useReducer { state: initialState, effects: [] } (_.state >>> reducer)
useEffect (UnsafeReference effects) do
for_ effects \aff ->
launchAff_ do
actions <- aff
liftEffect do for_ actions dispatch
mempty
pure (state /\ dispatch)

type AffReducer state action
= state ->
action ->
{ state :: state
, effects :: Array (Aff (Array action))
}

noEffects ::
forall state action.
state ->
{ state :: state
, effects :: Array (Aff (Array action))
}
noEffects state = { state, effects: [] }

newtype UseAffReducer state action hooks
= UseAffReducer
( UseEffect (UnsafeReference (Array (Aff (Array action))))
(UseReducer { state :: state, effects :: Array (Aff (Array action)) } action hooks)
)

derive instance ntUseAffReducer :: Newtype (UseAffReducer state action hooks) _

0 comments on commit d18d31b

Please sign in to comment.