@@ -3,17 +3,14 @@ module Yoga.React.Om
33 , omComponent
44 , useCtx
55 , useOm
6− , useSignal
7− , useEvent
6+ , useEffectOm
87 , UseCtx
98 , UseOm
10− , UseSignal
11− , UseEvent
9+ , UseEffectOm
1210 , liftRender
1311 , bind
1412 , discard
1513 , pure
16− -- Re-exported React hooks, lifted into OmRender
1714 , useState
1815 , useState'
1916 , useEffect
@@ -39,52 +36,40 @@ import Control.Apply.Indexed (class IxApply)
3936import Control.Bind.Indexed (class IxBind, ibind)
4037import Control.Monad.Indexed (class IxMonad)
4138import Data.Functor.Indexed (class IxFunctor)
42−import Data.Maybe (Maybe(..))
4339import Data.Newtype (class Newtype, un)
40+import Data.Either (Either(..))
41+import Data.Maybe (Maybe(..))
4442import Data.Tuple.Nested ((/\), type (/\))
45−import Control.Monad.ST.Class (liftST)
4643import Effect (Effect)
44+import Effect.Aff (error, killFiber, launchAff, launchAff_, try)
45+import Effect.Aff as Aff
4746import Effect.Class (liftEffect)
4847import Effect.Ref as Ref
4948import Effect.Unsafe (unsafePerformEffect)
50−import FRP.Event as Event
5149import React.Basic (JSX)
5250import React.Basic.Hooks (Reducer, UseState, UseEffect, UseLayoutEffect, UseRef, UseMemo, UseReducer, UseId, UseTransition, UseDeferredValue) as ReExports
5351import React.Basic.Hooks as Hooks
5452import React.Basic.Hooks.Internal (Render, unsafeHook, unsafeRenderEffect)
5553import React.Basic.Hooks.Internal as Render
56−import Yoga.React.Om.Signal (Signal)
5754import Type.Equality (class TypeEquals)
5855import Unsafe.Coerce (unsafeCoerce)
5956import Yoga.Om (Om)
6057import Yoga.Om as Om
6158
62−-- | A render context that tracks the Om context at the type level.
63−-- | At runtime this is identical to `Render` — the ctx is phantom.
64−-- | Use qualified-do with this module (e.g. `Om.do`) to compose hooks.
6559newtype OmRender :: Row Type -> Type -> Type -> Type -> Type
6660newtype OmRender ctx x y a = OmRender (Render x y a)
6761
6862derive instance Newtype (OmRender ctx x y a) _
6963
70−-- The stash: a mutable slot written by omComponent before each render,
71−-- read by useCtx/useOm during render. Safe because React renders are
72−-- synchronous and single-threaded.
7364ctxStash :: Ref.Ref (forall r. { | r })
7465ctxStash = unsafePerformEffect (Ref.new (unsafeCoerce {}))
7566
76−-- Helper: unwrap OmRender to the raw Effect
7767toEffect :: forall ctx x y a. OmRender ctx x y a -> Effect a
7868toEffect = unsafeCoerce
7969
80−-- Helper: wrap raw Effect into OmRender
8170fromEffect :: forall ctx x y a. Effect a -> OmRender ctx x y a
8271fromEffect = unsafeCoerce
8372
84−-- ============================================================================
85−-- Indexed monad instances (for qualified-do)
86−-- ============================================================================
87−
8873instance IxFunctor (OmRender ctx) where
8974 imap f m = fromEffect (map f (toEffect m))
9075
@@ -99,10 +84,6 @@ instance IxBind (OmRender ctx) where
9984
10085instance IxMonad (OmRender ctx)
10186
102−-- ============================================================================
103−-- Standard monad instances (when x ~ y, for `pure`)
104−-- ============================================================================
105−
10687instance Functor (OmRender ctx x y) where
10788 map f m = fromEffect (map f (toEffect m))
10889
@@ -123,10 +104,6 @@ instance (TypeEquals x y, Semigroup a) => Semigroup (OmRender ctx x y a) where
123104instance (TypeEquals x y, Monoid a) => Monoid (OmRender ctx x y a) where
124105 mempty = fromEffect mempty
125106
126−-- ============================================================================
127−-- Qualified-do exports
128−-- ============================================================================
129−
130107bind :: forall a b x y z ctx. OmRender ctx x y a -> (a -> OmRender ctx y z b) -> OmRender ctx x z b
131108bind = ibind
132109
@@ -136,22 +113,12 @@ discard = ibind
136113pure :: forall a x ctx. a -> OmRender ctx x x a
137114pure a = fromEffect (Prelude.pure a)
138115
139−-- ============================================================================
140−-- Lifting
141−-- ============================================================================
142−
143−-- | Lift a standard React hook into OmRender.
144116liftRender :: forall ctx x y a. Render x y a -> OmRender ctx x y a
145117liftRender = OmRender
146118
147−-- ============================================================================
148−-- Hooks
149−-- ============================================================================
150−
151119data UseCtx :: Row Type -> Type -> Type
152120data UseCtx ctx hooks
153121
154−-- | Read the full Om context record inside a component.
155122useCtx :: forall @ctx hooks. OmRender ctx hooks (UseCtx ctx hooks) { | ctx }
156123useCtx = OmRender (unsafeHook readCtx)
157124 where
@@ -161,55 +128,43 @@ useCtx = OmRender (unsafeHook readCtx)
161128data UseOm :: Row Type -> Type -> Type -> Type
162129data UseOm ctx a hooks
163130
164−-- | Run a synchronous Effect that has access to the Om context.
165131useOm
166− :: forall @ctx hooks a
167− . ({ | ctx } -> Effect a)
168− -> OmRender ctx hooks (UseOm ctx a hooks) a
169−useOm f = OmRender (unsafeHook go)
170− where
171− go :: Effect a
172− go = Prelude.do
173− ctx :: { | ctx } <- unsafeCoerce (Ref.read ctxStash)
174− f ctx
175−
176−data UseSignal :: Type -> Type -> Type
177−data UseSignal a hooks
178−
179−useSignal
180− :: forall ctx hooks a
181− . Signal a
182− -> OmRender ctx hooks (UseSignal a hooks) a
183−useSignal signal = fromEffect Prelude.do
184− initial <- signal.read
185− val /\ setVal <- toEffect (liftRender (Hooks.useState initial))
186− toEffect
187− ( liftRender
188− ( Hooks.useEffectOnce Prelude.do
189− Event.subscribe signal.event (\new -> setVal (const new)) # liftST # map liftST
190− )
191− )
192− Prelude.pure val
193−
194−data UseEvent :: Type -> Type -> Type
195−data UseEvent a hooks
196−
197−useEvent
198− :: forall ctx hooks a
199− . Event.Event a
200− -> OmRender ctx hooks (UseEvent a hooks) (Maybe a)
201−useEvent event = fromEffect Prelude.do
202− val /\ setVal <- toEffect (liftRender (Hooks.useState Nothing))
203− toEffect
204− ( liftRender
205− ( Hooks.useEffectOnce Prelude.do
206− Event.subscribe event (\new -> setVal (const (Just new))) # liftST # map liftST
207− )
208− )
209− Prelude.pure val
210−-- ============================================================================
211−-- Re-exported React hooks, lifted into OmRender
212−-- ============================================================================
132+ :: forall ctx deps a hooks
133+ . Eq deps
134+ => deps
135+ -> Om { | ctx } () a
136+ -> OmRender ctx hooks (UseOm ctx a hooks) (Maybe a)
137+useOm deps om = fromEffect Prelude.do
138+ ctx :: { | ctx } <- unsafeCoerce (Ref.read ctxStash)
139+ result /\ setResult <- toEffect (useState Nothing)
140+ toEffect $ useEffect deps Prelude.do
141+ setResult (const Nothing)
142+ fiber <- launchAff Prelude.do
143+ r <- try (Om.runOm ctx { exception: \e -> Aff.throwError e } om)
144+ liftEffect (setResult \_ -> Just r)
145+ Prelude.pure Prelude.do
146+ launchAff_ (killFiber (error "Stale request cancelled") fiber)
147+ toEffect $ liftRender $ unsafeRenderEffect case result of
148+ Just (Left err) -> Aff.throwError err
149+ Just (Right a) -> Prelude.pure (Just a)
150+ Nothing -> Prelude.pure Nothing
151+
152+data UseEffectOm :: Row Type -> Type -> Type
153+data UseEffectOm ctx hooks
154+
155+useEffectOm
156+ :: forall ctx deps hooks
157+ . Eq deps
158+ => deps
159+ -> Om { | ctx } () Unit
160+ -> OmRender ctx hooks (UseEffectOm ctx hooks) Unit
161+useEffectOm deps om = fromEffect Prelude.do
162+ ctx :: { | ctx } <- unsafeCoerce (Ref.read ctxStash)
163+ toEffect $ useEffect deps Prelude.do
164+ fiber <- launchAff Prelude.do
165+ Om.runOm ctx { exception: \_ -> Prelude.pure unit } om
166+ Prelude.pure Prelude.do
167+ launchAff_ (killFiber (error "Stale request cancelled") fiber)
213168
214169useState
215170 :: forall ctx state hooks
@@ -298,13 +253,6 @@ useDeferredValue
298253 -> OmRender ctx hooks (Hooks.UseDeferredValue a hooks) a
299254useDeferredValue a = liftRender (Hooks.useDeferredValue a)
300255
301−-- ============================================================================
302−-- Component construction
303−-- ============================================================================
304−
305−-- | Create a React component with Om-based dependency injection.
306−-- | The context is captured once at construction time and made available
307−-- | to hooks via `useCtx` and `useOm` inside qualified-do blocks.
308256omComponent
309257 :: forall @ctx @err hooks props
310258 . String