Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial hooks implementation #159

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"purescript-exceptions": "^4.0.0",
"purescript-maybe": "^4.0.0",
"purescript-nullable": "^4.0.0",
"purescript-typelevel-prelude": "^3.0.0"
"purescript-typelevel-prelude": "^3.0.0",
"purescript-tuples": "^5.1.0"
},
"devDependencies": {
"purescript-console": "^4.1.0",
Expand Down
13 changes: 0 additions & 13 deletions src/React.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,6 @@ function getProps(this_) {
}
exports.getProps = getProps;

exports.childrenToArray = React.Children.toArray

exports.childrenCount = React.Children.count;

function setStateImpl(this_) {
return function(state){
return function(){
Expand Down Expand Up @@ -160,12 +156,3 @@ function createElementDynamic(class_) {
};
exports.createElementDynamicImpl = createElementDynamic;
exports.createElementTagNameDynamic = createElementDynamic;

function createContext(defaultValue) {
var context = React.createContext(defaultValue);
return {
consumer: context.Consumer,
provider: context.Provider
};
}
exports.createContext = createContext;
160 changes: 88 additions & 72 deletions src/React.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

module React
( TagName
, ReactElement
, ReactComponent
, ReactThis
, ReactUnusedSnapshot
Expand Down Expand Up @@ -31,8 +30,6 @@ module React
, pureComponent
, pureComponentWithDerivedState
, statelessComponent
, ReactClass
, ReactRef
, getProps
, getState
, setState
Expand All @@ -53,40 +50,42 @@ module React
, unsafeCreateLeafElement
, createElementTagName
, createElementTagNameDynamic
, Children
, childrenToArray
, childrenCount
, class IsReactElement
, toElement
, fragmentWithKey
, Context
, ContextProvider
, ContextConsumer
, createContext
, createHookElement
, unsafeCreateHookElement
, createHookElementDynamic
, unsafeCreateHookElementDynamic
, createHookLeafElement
, unsafeCreateHookLeafElement
, createRenderPropsElement
, module Exports
) where

import Prelude

import Data.Nullable (Nullable)
import Effect (Effect)
import Effect.Exception (Error)
import Effect.Uncurried (EffectFn1)
import Prim.Row as Row
import Type.Row (type (+))
import Unsafe.Coerce (unsafeCoerce)

import React.Hook (Hook)
import React.Ref (Ref, DOMRef)
import React.Types (ReactElement, ReactClass, Children)
import React.Types
( ReactClass
, ReactElement
, class IsReactElement
, toElement
, Children
, childrenToArray
, childrenCount
) as Exports

-- | Name of a tag.
type TagName = String

-- | A virtual DOM node, or component.
foreign import data ReactElement :: Type

instance semigroupReactElement :: Semigroup ReactElement where
append a b = toElement [ a, b ]

instance monoidReactElement :: Monoid ReactElement where
mempty = toElement ([] :: Array ReactElement)

-- | A mounted react component
foreign import data ReactComponent :: Type

Expand Down Expand Up @@ -246,15 +245,8 @@ foreign import statelessComponent :: forall props.
(Record props -> ReactElement) ->
ReactClass (Record props)

-- | React class for components.
foreign import data ReactClass :: Type -> Type

foreign import fragment :: ReactClass { children :: Children }

-- | Type for React refs. This type is opaque, but you can use `Data.Foreign`
-- | and `DOM` to validate the underlying representation.
foreign import data ReactRef :: Type

-- | Read the component props.
foreign import getProps :: forall props state.
ReactThis props state ->
Expand Down Expand Up @@ -339,7 +331,7 @@ class ReactPropFields (required :: # Type) (given :: # Type)

type ReservedReactPropFields r =
( key :: String
, ref :: SyntheticEventHandler (Nullable ReactRef)
, ref :: Ref DOMRef
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. A class component isn't going to have a DOMRef since it will be pointing to the instance. As is, the only thing that would make sense to me is Foreign. Since we are redoing refs anyway, maybe there's a better way to handle this side of the interface in a typed way... I'm not sure though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. It could be the instance. I wouldn't mind Foreign, but what if we made an opaque type ForwardRef instead? I am not sure if there is a better way to hand this, but I am open to ideas.

| r
)

Expand Down Expand Up @@ -386,7 +378,7 @@ unsafeCreateElementDynamic :: forall props.
unsafeCreateElementDynamic = createElementDynamicImpl

foreign import createElementImpl :: forall required given children.
ReactClass required -> given -> Array children -> ReactElement
ReactClass required -> given -> children -> ReactElement

foreign import createElementDynamicImpl :: forall required given children.
ReactClass required -> given -> Array children -> ReactElement
Expand Down Expand Up @@ -418,48 +410,72 @@ foreign import createElementTagName :: forall props.
foreign import createElementTagNameDynamic :: forall props.
TagName -> props -> Array ReactElement -> ReactElement

-- | Internal representation for the children elements passed to a component
foreign import data Children :: Type

-- | Internal conversion function from children elements to an array of React elements
foreign import childrenToArray :: Children -> Array ReactElement

-- | Returns the number of children.
foreign import childrenCount :: Children -> Int

class IsReactElement a where
toElement :: a -> ReactElement

instance isReactElementString :: IsReactElement String where
toElement = unsafeCoerce

instance isReactElementNumber :: IsReactElement Number where
toElement = unsafeCoerce

instance isReactElementInt :: IsReactElement Int where
toElement = unsafeCoerce

instance isReactElementChildren :: IsReactElement Children where
toElement = unsafeCoerce

instance isReactElementReactElement :: IsReactElement ReactElement where
toElement = identity

instance isReactElementArray :: IsReactElement (Array ReactElement) where
toElement = createElement fragment {}

-- | Creates a keyed fragment.
fragmentWithKey :: String -> Array ReactElement -> ReactElement
fragmentWithKey = createElement fragment <<< { key: _ }

type Context a =
{ consumer :: ContextConsumer a
, provider :: ContextProvider a
}

type ContextProvider a = ReactClass { children :: Children, value :: a }

type ContextConsumer a = ReactClass { children :: a -> ReactElement }

-- | Create a new context provider/consumer pair given a default value.
foreign import createContext :: forall a. a -> Context a
-- | Create an element from a function using Hooks spreading the children array. Used when the children are known up front.
createHookElement
:: forall required given
. ReactPropFields required given
=> ({ children :: Children | required } -> Hook ReactElement)
-> { | given }
-> Array ReactElement
-> ReactElement
createHookElement k = createElementImpl (unsafeCoerce k)

-- | An unsafe version of `createHookElement` which does not enforce the reserved properties "key" and "ref".
unsafeCreateHookElement
:: forall props
. ({ children :: Children | props } -> Hook ReactElement)
-> { | props }
-> Array ReactElement
-> ReactElement
unsafeCreateHookElement k = createElementImpl (unsafeCoerce k)

-- | Create an element from a function using Hooks passing the children array. Used for a dynamic array of children.
createHookElementDynamic
:: forall required given
. ReactPropFields required given
=> ({ children :: Children | required } -> Hook ReactElement)
-> { | given }
-> Array ReactElement
-> ReactElement
createHookElementDynamic k = createElementDynamicImpl (unsafeCoerce k)

-- | An unsafe version of `createHookElementDynamic` which does not enforce the reserved properties "key" and "ref".
unsafeCreateHookElementDynamic
:: forall props
. ({ children :: Children | props } -> Hook ReactElement)
-> { | props }
-> Array ReactElement
-> ReactElement
unsafeCreateHookElementDynamic k = createElementDynamicImpl (unsafeCoerce k)

-- | Create an element from a function using Hooks that does not require children.
createHookLeafElement
:: forall required given
. ReactPropFields required given
=> ({ | required } -> Hook ReactElement)
-> { | given }
-> ReactElement
createHookLeafElement k = createLeafElementImpl (unsafeCoerce k)

-- | An unsafe version of `createHookLeafElement` which does not enforce the reserved
-- | properties "key" and "ref".
unsafeCreateHookLeafElement
:: forall props
. ({ | props } -> Hook ReactElement)
-> { | props }
-> ReactElement
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createHookElement like createLeafElement?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this all uses createElement under the hood, this isn't parametric due to key and ref props.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good for createHookElement. Noted on key and ref.

unsafeCreateHookLeafElement k = createLeafElementImpl (unsafeCoerce k)

-- | Create an element using the [render props pattern](https://reactjs.org/docs/render-props.html#using-props-other-than-render) when the name of the render prop is "children".
createRenderPropsElement
:: forall required given childrenProps
. ReactPropFields required given
=> ReactClass { children :: childrenProps -> ReactElement | required }
-> { | given }
-> (childrenProps -> ReactElement)
-> ReactElement
createRenderPropsElement = createElementImpl
18 changes: 18 additions & 0 deletions src/React/Context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

var React = require('react');

exports.getProvider = function getProvider(context) {
return context.Provider;
};

exports.getConsumer = function getConsumer(context) {
return context.Consumer;
};

exports.createContext_ = function createContext_(defaultValue, calculateChangedBits) {
return calculateChangedBits ?
React.createContext(defaultValue, calculateChangedBits) :
React.createContext(defaultValue)
;
};
30 changes: 30 additions & 0 deletions src/React/Context.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module React.Context
( Context
, createContext
, getProvider
, getConsumer
) where

import Prelude

import Data.Function.Uncurried (Fn2, mkFn2, runFn2)
import Data.Maybe (Maybe)
import Data.Nullable (Nullable)
import Data.Nullable as Nullable

import React.Types (ReactElement, ReactClass, Children)

createContext :: forall a. a -> Maybe (a -> a -> Number) -> Context a
createContext a = runFn2 createContext_ a <<< Nullable.toNullable <<< map mkFn2

foreign import data Context :: Type -> Type

foreign import getProvider :: forall a. Context a -> ReactClass { children :: Children, value :: a }

foreign import getConsumer :: forall a. Context a -> ReactClass { children :: a -> ReactElement }

foreign import createContext_
:: forall a
. Fn2 a
(Nullable (Fn2 a a Number))
(Context a)
9 changes: 6 additions & 3 deletions src/React/DOM/Props.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Prelude
import Data.Nullable (Nullable)
import Effect (Effect)
import Effect.Uncurried (mkEffectFn1)
import React (ReactRef)
import React.Ref (Ref, DOMRef)
import React.SyntheticEvent
( SyntheticEvent
, SyntheticAnimationEvent
Expand Down Expand Up @@ -894,8 +894,11 @@ onScrollCapture f = unsafeMkProps "onScrollCapture" (mkEffectFn1 f)
onWheelCapture :: (SyntheticWheelEvent -> Effect Unit) -> Props
onWheelCapture f = unsafeMkProps "onWheelCapture" (mkEffectFn1 f)

ref :: (Nullable ReactRef -> Effect Unit) -> Props
ref f = unsafeMkProps "ref" (mkEffectFn1 f)
ref :: Ref DOMRef -> Props
ref = unsafeMkProps "ref"

callbackRef :: (Nullable DOMRef -> Effect Unit) -> Props
callbackRef f = unsafeMkProps "ref" (mkEffectFn1 f)

suppressContentEditableWarning :: Boolean -> Props
suppressContentEditableWarning = unsafeMkProps "suppressContentEditableWarning"
Expand Down
Loading