From 708d13d5c18008cf906c792714a6274b07b62a5f Mon Sep 17 00:00:00 2001 From: Ryan Trinkle Date: Tue, 4 Jul 2023 11:35:18 -0400 Subject: [PATCH] Clean up and reorganize --- react.cabal | 49 +++----- src/React.hs | 279 ++--------------------------------------- src/React/Component.hs | 23 ++++ src/React/Element.hs | 26 ++++ src/React/Export.hs | 64 ++++++++++ src/React/Hook.hs | 118 +++++++++++++++++ src/React/JSaddle.hs | 92 ++++++++++++++ src/React/Misc.hs | 11 ++ src/React/Types.hs | 37 ++++++ 9 files changed, 394 insertions(+), 305 deletions(-) create mode 100644 src/React/Component.hs create mode 100644 src/React/Element.hs create mode 100644 src/React/Export.hs create mode 100644 src/React/Hook.hs create mode 100644 src/React/JSaddle.hs create mode 100644 src/React/Misc.hs create mode 100644 src/React/Types.hs diff --git a/react.cabal b/react.cabal index fb154b3..4028541 100644 --- a/react.cabal +++ b/react.cabal @@ -10,40 +10,27 @@ build-type: Simple extra-doc-files: CHANGELOG.md common warnings - ghc-options: -Wall + ghc-options: -Wall -Wunused-packages library import: warnings exposed-modules: React + React.Component + React.Element + React.Export + React.Hook + React.JSaddle + React.Misc + React.Types - build-depends: base - , jsaddle - , jsaddle-dom - , ghcjs-dom - , text - , containers - , mtl - , deepseq - , aeson - , scientific - , bytestring - , stm - , ref-tf - , transformers - , unliftio-core - , exceptions - , async - , primitive - , lens - , ghc-prim - , http-types - , entropy - , base64-bytestring - , witherable - , foreign-store - , dependent-sum - , reflection + build-depends: + base + , containers + , jsaddle + , mtl + , template-haskell + , text hs-source-dirs: src @@ -54,10 +41,6 @@ library -- the hashable should to be pinned in reflex-platform , hashable == 1.3.5.0 else - build-depends: websockets - , wai - , wai-websockets - , warp - , jsaddle-warp + build-depends: jsaddle-warp default-language: Haskell2010 diff --git a/src/React.hs b/src/React.hs index 4480580..685bfc5 100644 --- a/src/React.hs +++ b/src/React.hs @@ -1,273 +1,8 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ApplicativeDo #-} -{-# LANGUAGE TypeApplications #-} -{-# OPTIONS_GHC -Wno-orphans #-} -module React where +module React (module X) where -import Prelude hiding ((!!)) - -import Data.Text (Text) -import qualified Data.Text as T -import Data.Map (Map) -import qualified Data.Map as Map -import Control.Monad -import Control.Monad.Except -import Control.Monad.Reader -import Data.String - -#ifdef ghcjs_HOST_OS -import Data.Coerce (coerce) -import GHCJS.Foreign.Callback -import qualified JavaScript.Array as Array (toListIO) -import Language.Javascript.JSaddle -#else -import GHCJS.Prim.Internal (primToJSVal) -import qualified Data.Text.IO as T -import Language.Javascript.JSaddle hiding (Ref) -#endif - -t :: Text -> Text -t = id - -tshow :: Show a => a -> Text -tshow = T.pack . show - -#ifndef ghcjs_HOST_OS -printJavaScriptException :: JavaScriptException -> JSM () -printJavaScriptException (JavaScriptException e) = do - s <- e # t "toString" $ () - j <- valToJSON s - liftIO $ T.putStrLn $ "Exception: " <> tshow j -#endif - -#ifndef ghcjs_HOST_OS -instance PToJSVal Text where - pToJSVal s = primToJSVal $ PrimVal_String s - -instance PToJSVal Int where - pToJSVal i = primToJSVal $ PrimVal_Number $ fromIntegral i -#endif - -instance IsString JSVal where - fromString = pToJSVal . T.pack - -instance ToJSVal v => ToJSVal (Map Text v) where - toJSVal m = do - o@(Object oVal) <- obj - forM_ (Map.toList m) $ \(k, v) -> do - (o <# k) =<< toJSVal v - pure oVal - -consoleLog :: ToJSVal a => a -> JSM JSVal -consoleLog x = (global ! t "console") # t "log" $ [x] - -instance ToJSVal (Component props refVal) where - toJSVal (Component f) = toJSVal f - -instance PToJSVal (Component props refVal) where - pToJSVal (Component f) = pToJSVal f - -instance PToJSVal Function where - pToJSVal (Function _ o) = pToJSVal o - -instance PToJSVal Object where - pToJSVal (Object v) = v - -newtype Component props refVal = Component { unComponent :: Function' } - -newtype Hook a = Hook { unHook :: ReaderT React JSM a } - deriving (Functor, Applicative, Monad) - -newtype Render a = Render { unRender :: ReaderT React JSM a } - deriving (Functor, Applicative, Monad) - --- | An object that contains the React library -newtype React = React { unReact :: Object } - -instance MakeObject React where - makeObject = pure . unReact - -instance MakeObject (Component props refVal) where - makeObject = makeObject . functionObject' . unComponent - -newtype Element = Element { unElement :: ReaderT React JSM JSVal } - -instance IsString Element where - fromString = Element . pure . pToJSVal . T.pack - -newtype Tag = Tag { unTag :: JSVal } - -instance IsString Tag where - fromString = Tag . pToJSVal . T.pack - -createElement :: Tag -> Map Text JSVal -> [Element] -> Element -createElement etag props children = Element $ do - react <- ask - createdChildren <- mapM unElement children - lift $ react # t "createElement" $ [pure $ unTag etag, toJSVal props] <> fmap pure createdChildren - -createFragment :: [Element] -> Element -createFragment = createFragmentWithProps mempty - -createFragmentWithProps :: Map Text JSVal -> [Element] -> Element -createFragmentWithProps props children = Element $ do - react <- ask - fragmentTag <- lift $ fmap Tag $ react ! t "Fragment" - unElement $ createElement fragmentTag props children - ---TODO: The Hook section shouldn't have any control flow to it; probably it also shouldn't depend on props except in specific ways -component :: Hook (JSVal -> Render Element) -> ReaderT React JSM (Component JSVal ()) -component (Hook hook) = do - react <- ask - f <- lift $ function' $ \_ _ args -> flip runReaderT react $ do - render <- hook - let props = case args of - [] -> jsUndefined - arg0 : _ -> arg0 - e <- unRender $ render props - unElement e - pure $ Component f - ---TODO: Input can be an initializer function rather than value ---TODO: `set` can take `a -> a` instead of `a` -useState :: (ToJSVal a, FromJSVal a) => a -> Hook (a, a -> JSM ()) -useState initialValue = Hook $ do - react <- ask - initialJSVal <- lift $ toJSVal initialValue - result <- lift $ (react # t "useState") initialJSVal - Just s <- lift $ fromJSVal =<< result !! 0 --TODO: Exception handling - setter <- lift $ result !! 1 - pure - ( s - , \v' -> void $ call setter nullObject [v'] - ) - -useRef :: JSVal -> Hook JSVal -useRef initialValue = Hook $ do - react <- ask - lift $ (react # t "useRef") initialValue - -useEffect :: (JSVal -> JSVal -> [JSVal] -> JSM JSVal) -> Maybe [JSVal] -> Hook () -useEffect f deps = Hook $ do - react <- ask - Function' _ cb <- lift $ function' f - depsArg <- case deps of - Nothing -> pure [] - Just someDeps -> do - depsArray <- lift $ toJSVal someDeps - pure [depsArray] - _ <- lift $ (react # t "useEffect") $ [pToJSVal cb] <> depsArg - pure () - -useMemo :: (ToJSVal a, FromJSVal a) => JSM a -> Maybe [JSVal] -> Hook a -useMemo a deps = Hook $ do - react <- ask - Function' _ cb <- lift $ function' $ \_ _ _ -> toJSVal =<< a - depsArg <- case deps of - Nothing -> pure [] - Just someDeps -> do - depsArray <- lift $ toJSVal someDeps - pure [depsArray] - resultVal <- lift $ (react # t "useMemo") $ [pToJSVal cb] <> depsArg - Just result <- lift $ fromJSVal resultVal - pure result - -useCallback :: ToJSVal result => (JSVal -> JSVal -> [JSVal] -> JSM result) -> Maybe [JSM JSVal] -> Hook JSVal -useCallback f deps = Hook $ do - react <- ask - Function' _ cb <- lift $ function' $ \fObj this args -> toJSVal =<< f fObj this args - depsArg <- case deps of - Nothing -> pure [] - Just someDeps -> do - depsArray <- lift $ toJSVal =<< sequence someDeps - pure [depsArray] - lift $ (react # t "useCallback") $ [pToJSVal cb] <> depsArg - --------------------------------------------------------------------------------- --- Not yet supported --------------------------------------------------------------------------------- - -type DispatchFunction a = a -> Effect () - -type Reducer s a = s -> a -> s - -useContext :: Context a -> Hook a -useContext = undefined - -data Context a - -createContext :: a -> IO (Context a) -createContext = undefined - -provider :: Context a -> a -> Render b -> Render b -provider = undefined - -data Ref a - -forwardRef :: (props -> Ref refVal -> Hook (Render ())) -> Component props refVal -forwardRef = undefined - -useImperativeHandle :: Ref a -> Effect a -> Maybe [JSVal] -> Hook () -useImperativeHandle = undefined - -useReducer :: Reducer s a -> a -> Maybe (a -> a) -> Hook (a, DispatchFunction a) -useReducer = undefined - -useTransition :: Hook (Bool, Effect () -> Effect ()) -useTransition = undefined - -useDeferredValue :: a -> Hook a -useDeferredValue = undefined - -useDebugValue :: a -> Maybe (a -> b) -> Hook () -useDebugValue = undefined - -useId :: Hook Text -useId = undefined - -useSyncExternalStore :: (IO () -> IO (IO ())) -> IO a -> Maybe (IO a) -> Hook () -useSyncExternalStore = undefined - -newtype Effect a = Effect { unEffect :: JSM a } - deriving (Functor, Applicative, Monad) - --- TODO: Add the following in jsaddle -type JSCallAsFunction' = JSVal -- ^ Function object - -> JSVal -- ^ this - -> [JSVal] -- ^ Function arguments - -> JSM JSVal -- ^ Return value - -function' :: JSCallAsFunction' -- ^ Haskell function to call - -> JSM Function' -- ^ Returns a JavaScript function object that will - -- call the Haskell one when it is called -#ifdef ghcjs_HOST_OS -function' f = do - callback <- syncCallback2' $ \this args -> do - rargs <- Array.toListIO (coerce args) - f this this rargs -- TODO pass function object through - Function' callback <$> makeFunctionWithCallback' callback -#else -function' f = do - (cb, f') <- newSyncCallback'' f --TODO: "ContinueAsync" behavior - return $ Function' cb $ Object f' -#endif - -#ifdef ghcjs_HOST_OS -data Function' = Function' {functionCallback' :: Callback (JSVal -> JSVal -> IO JSVal), functionObject' :: Object} -#else -data Function' = Function' {functionCallback' :: CallbackId, functionObject' :: Object} -#endif - -#ifdef ghcjs_HOST_OS -foreign import javascript unsafe "$r = function () { return $1(this, arguments); }" - makeFunctionWithCallback' :: Callback (JSVal -> JSVal -> IO JSVal) -> IO Object -#endif - -instance ToJSVal Function' where - toJSVal = toJSVal . functionObject' - -instance PToJSVal Function' where - pToJSVal (Function' _ o) = pToJSVal o +import React.Component as X +import React.JSaddle as X +import React.Misc as X +import React.Types as X +import React.Hook as X +import React.Element as X diff --git a/src/React/Component.hs b/src/React/Component.hs new file mode 100644 index 0000000..4431053 --- /dev/null +++ b/src/React/Component.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module React.Component where + +import Control.Monad.Except +import Control.Monad.Reader +import Language.Javascript.JSaddle hiding (Ref) + +import React.JSaddle +import React.Types + +--TODO: The Hook section shouldn't have any control flow to it; probably it also shouldn't depend on props except in specific ways +component :: Hook (JSVal -> Render Element) -> ReaderT React JSM (Component JSVal ()) +component (Hook hook) = do + react <- ask + f <- lift $ function' $ \_ _ args -> flip runReaderT react $ do + render <- hook + let props = case args of + [] -> jsUndefined + arg0 : _ -> arg0 + e <- unRender $ render props + unElement e + pure $ Component f + diff --git a/src/React/Element.hs b/src/React/Element.hs new file mode 100644 index 0000000..c8f09af --- /dev/null +++ b/src/React/Element.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE OverloadedStrings #-} +module React.Element where + +import Control.Monad.Except +import Control.Monad.Reader +import Data.Map (Map) +import Data.Text (Text) +import Language.Javascript.JSaddle hiding (Ref) + +import React.Misc +import React.Types + +createElement :: Tag -> Map Text JSVal -> [Element] -> Element +createElement etag props children = Element $ do + react <- ask + createdChildren <- mapM unElement children + lift $ react # t "createElement" $ [pure $ unTag etag, toJSVal props] <> fmap pure createdChildren + +createFragment :: [Element] -> Element +createFragment = createFragmentWithProps mempty + +createFragmentWithProps :: Map Text JSVal -> [Element] -> Element +createFragmentWithProps props children = Element $ do + react <- ask + fragmentTag <- lift $ fmap Tag $ react ! t "Fragment" + unElement $ createElement fragmentTag props children diff --git a/src/React/Export.hs b/src/React/Export.hs new file mode 100644 index 0000000..2b180a9 --- /dev/null +++ b/src/React/Export.hs @@ -0,0 +1,64 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} +module React.Export where + +import Control.Monad.Except +import Control.Monad.Reader as MTL +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import Language.Haskell.TH +import Language.Haskell.TH.Syntax as TH +import Language.Javascript.JSaddle + +import React.Misc +import React.JSaddle () +import React.Types + +#ifndef ghcjs_HOST_OS +import Language.Javascript.JSaddle.Warp +#endif + +mainExportsToJS :: [Name] -> Q [Dec] +mainExportsToJS names = [d| + main :: IO () + main = exportToJSIO $ sequence $ Map.fromList $(listE $ fmap nameToExportEntry names) + |] + +nameToExportEntry :: Name -> Q Exp +nameToExportEntry n = [| (T.pack $(TH.lift $ nameBase n), MTL.lift . toJSVal =<< $(varE n)) |] + +exportToJSIO :: ReaderT React JSM (Map Text JSVal) -> IO () +exportToJSIO build = runJS $ \arg -> do + react <- fmap (React . Object) $ arg ! t "react" + m <- flip runReaderT react build + _ <- (arg # t "setVal") [m] + pure () + +printJavaScriptException :: JavaScriptException -> JSM () +printJavaScriptException (JavaScriptException e) = do + s <- e # t "toString" $ () + j <- valToJSON s + liftIO $ T.putStrLn $ "Exception: " <> tshow j + +runJS :: (JSVal -> JSM ()) -> IO () + +#ifdef ghcjs_HOST_OS + +foreign import javascript unsafe "getProgramArg" + getProgramArg :: JSM JSVal + +runJS f = do + arg <- getProgramArg + f arg `catchError` printJavaScriptException + +#else + +runJS f = do + let port = 3001 --TODO: Get this from npm config or something + run port $ \arg -> f arg `catchError` printJavaScriptException + +#endif diff --git a/src/React/Hook.hs b/src/React/Hook.hs new file mode 100644 index 0000000..fbc98e7 --- /dev/null +++ b/src/React/Hook.hs @@ -0,0 +1,118 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module React.Hook where + +import Prelude hiding ((!!)) + +import Control.Monad +import Control.Monad.Except +import Control.Monad.Reader +import Data.Text (Text) +import Language.Javascript.JSaddle hiding (Ref) + +import React.JSaddle +import React.Misc +import React.Types + +--TODO: Input can be an initializer function rather than value +--TODO: `set` can take `a -> a` instead of `a` +useState :: (ToJSVal a, FromJSVal a) => a -> Hook (a, a -> JSM ()) +useState initialValue = Hook $ do + react <- ask + initialJSVal <- lift $ toJSVal initialValue + result <- lift $ (react # t "useState") initialJSVal + Just s <- lift $ fromJSVal =<< result !! 0 --TODO: Exception handling + setter <- lift $ result !! 1 + pure + ( s + , \v' -> void $ call setter nullObject [v'] + ) + +useRef :: JSVal -> Hook JSVal +useRef initialValue = Hook $ do + react <- ask + lift $ (react # t "useRef") initialValue + +useEffect :: (JSVal -> JSVal -> [JSVal] -> JSM JSVal) -> Maybe [JSVal] -> Hook () +useEffect f deps = Hook $ do + react <- ask + Function' _ cb <- lift $ function' f + depsArg <- case deps of + Nothing -> pure [] + Just someDeps -> do + depsArray <- lift $ toJSVal someDeps + pure [depsArray] + _ <- lift $ (react # t "useEffect") $ [pToJSVal cb] <> depsArg + pure () + +useMemo :: (ToJSVal a, FromJSVal a) => JSM a -> Maybe [JSVal] -> Hook a +useMemo a deps = Hook $ do + react <- ask + Function' _ cb <- lift $ function' $ \_ _ _ -> toJSVal =<< a + depsArg <- case deps of + Nothing -> pure [] + Just someDeps -> do + depsArray <- lift $ toJSVal someDeps + pure [depsArray] + resultVal <- lift $ (react # t "useMemo") $ [pToJSVal cb] <> depsArg + Just result <- lift $ fromJSVal resultVal + pure result + +useCallback :: ToJSVal result => (JSVal -> JSVal -> [JSVal] -> JSM result) -> Maybe [JSM JSVal] -> Hook JSVal +useCallback f deps = Hook $ do + react <- ask + Function' _ cb <- lift $ function' $ \fObj this args -> toJSVal =<< f fObj this args + depsArg <- case deps of + Nothing -> pure [] + Just someDeps -> do + depsArray <- lift $ toJSVal =<< sequence someDeps + pure [depsArray] + lift $ (react # t "useCallback") $ [pToJSVal cb] <> depsArg + +-------------------------------------------------------------------------------- +-- Not yet supported +-------------------------------------------------------------------------------- + +type DispatchFunction a = a -> Effect () + +type Reducer s a = s -> a -> s + +useContext :: Context a -> Hook a +useContext = undefined + +data Context a + +createContext :: a -> IO (Context a) +createContext = undefined + +provider :: Context a -> a -> Render b -> Render b +provider = undefined + +data Ref a + +forwardRef :: (props -> Ref refVal -> Hook (Render ())) -> Component props refVal +forwardRef = undefined + +useImperativeHandle :: Ref a -> Effect a -> Maybe [JSVal] -> Hook () +useImperativeHandle = undefined + +useReducer :: Reducer s a -> a -> Maybe (a -> a) -> Hook (a, DispatchFunction a) +useReducer = undefined + +useTransition :: Hook (Bool, Effect () -> Effect ()) +useTransition = undefined + +useDeferredValue :: a -> Hook a +useDeferredValue = undefined + +useDebugValue :: a -> Maybe (a -> b) -> Hook () +useDebugValue = undefined + +useId :: Hook Text +useId = undefined + +useSyncExternalStore :: (IO () -> IO (IO ())) -> IO a -> Maybe (IO a) -> Hook () +useSyncExternalStore = undefined + +newtype Effect a = Effect { unEffect :: JSM a } + deriving (Functor, Applicative, Monad) diff --git a/src/React/JSaddle.hs b/src/React/JSaddle.hs new file mode 100644 index 0000000..157107e --- /dev/null +++ b/src/React/JSaddle.hs @@ -0,0 +1,92 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -Wno-orphans #-} +-- | Everything in this module belongs in JSaddle, GHCJS-DOM, or similar +module React.JSaddle where + +import Prelude hiding ((!!)) + +import Data.Text (Text) +import qualified Data.Text as T +import Data.Map (Map) +import qualified Data.Map as Map +import Control.Monad +import Data.String + +import React.Misc + +#ifdef ghcjs_HOST_OS +import Data.Coerce (coerce) +import GHCJS.Foreign.Callback +import qualified JavaScript.Array as Array (toListIO) +import Language.Javascript.JSaddle +#else +import GHCJS.Prim.Internal (primToJSVal) +import Language.Javascript.JSaddle hiding (Ref) +#endif + +#ifndef ghcjs_HOST_OS +instance PToJSVal Text where + pToJSVal s = primToJSVal $ PrimVal_String s + +instance PToJSVal Int where + pToJSVal i = primToJSVal $ PrimVal_Number $ fromIntegral i +#endif + +instance PToJSVal Function where + pToJSVal (Function _ o) = pToJSVal o + +instance PToJSVal Object where + pToJSVal (Object v) = v + +instance IsString JSVal where + fromString = pToJSVal . T.pack + +instance ToJSVal v => ToJSVal (Map Text v) where + toJSVal m = do + o@(Object oVal) <- obj + forM_ (Map.toList m) $ \(k, v) -> do + (o <# k) =<< toJSVal v + pure oVal + +consoleLog :: ToJSVal a => a -> JSM JSVal +consoleLog x = (global ! t "console") # t "log" $ [x] + +type JSCallAsFunction' = JSVal -- ^ Function object + -> JSVal -- ^ this + -> [JSVal] -- ^ Function arguments + -> JSM JSVal -- ^ Return value + +function' :: JSCallAsFunction' -- ^ Haskell function to call + -> JSM Function' -- ^ Returns a JavaScript function object that will + -- call the Haskell one when it is called +#ifdef ghcjs_HOST_OS +function' f = do + callback <- syncCallback2' $ \this args -> do + rargs <- Array.toListIO (coerce args) + f this this rargs -- TODO pass function object through + Function' callback <$> makeFunctionWithCallback' callback +#else +function' f = do + (cb, f') <- newSyncCallback'' f --TODO: "ContinueAsync" behavior + return $ Function' cb $ Object f' +#endif + +#ifdef ghcjs_HOST_OS +data Function' = Function' {functionCallback' :: Callback (JSVal -> JSVal -> IO JSVal), functionObject' :: Object} +#else +data Function' = Function' {functionCallback' :: CallbackId, functionObject' :: Object} +#endif + +#ifdef ghcjs_HOST_OS +foreign import javascript unsafe "$r = function () { return $1(this, arguments); }" + makeFunctionWithCallback' :: Callback (JSVal -> JSVal -> IO JSVal) -> IO Object +#endif + +instance ToJSVal Function' where + toJSVal = toJSVal . functionObject' + +instance PToJSVal Function' where + pToJSVal (Function' _ o) = pToJSVal o diff --git a/src/React/Misc.hs b/src/React/Misc.hs new file mode 100644 index 0000000..52e4c04 --- /dev/null +++ b/src/React/Misc.hs @@ -0,0 +1,11 @@ +module React.Misc where + +import Data.Text (Text) +import qualified Data.Text as T + +t :: Text -> Text +t = id + +tshow :: Show a => a -> Text +tshow = T.pack . show + diff --git a/src/React/Types.hs b/src/React/Types.hs new file mode 100644 index 0000000..5b273fb --- /dev/null +++ b/src/React/Types.hs @@ -0,0 +1,37 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module React.Types where + +import Control.Monad.Reader +import Data.String +import qualified Data.Text as T +import Language.Javascript.JSaddle hiding (Ref) + +import React.JSaddle + +-- | An object that contains the React library +newtype React = React { unReact :: Object } + +instance MakeObject React where + makeObject = pure . unReact + +newtype Component props refVal = Component { unComponent :: Function' } + deriving (ToJSVal, PToJSVal) + +instance MakeObject (Component props refVal) where + makeObject = makeObject . functionObject' . unComponent + +newtype Hook a = Hook { unHook :: ReaderT React JSM a } + deriving (Functor, Applicative, Monad, MonadIO, MonadJSM) + +newtype Render a = Render { unRender :: ReaderT React JSM a } + deriving (Functor, Applicative, Monad, MonadIO, MonadJSM) + +newtype Element = Element { unElement :: ReaderT React JSM JSVal } + +instance IsString Element where + fromString = Element . pure . pToJSVal . T.pack + +newtype Tag = Tag { unTag :: JSVal } + +instance IsString Tag where + fromString = Tag . pToJSVal . T.pack