-
Notifications
You must be signed in to change notification settings - Fork 65
Initial hooks implementation #159
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
base: main
Are you sure you want to change the base?
Changes from all commits
90e61f0
0e5d81b
1088218
53d8011
102d044
a60a253
e29447e
e39a96c
140c3bc
4e456da
2c1a2cc
088d175
7b5ec57
e8b5738
edcd569
ba17b83
ebe5f47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,6 @@ | |
|
||
module React | ||
( TagName | ||
, ReactElement | ||
, ReactComponent | ||
, ReactThis | ||
, ReactUnusedSnapshot | ||
|
@@ -31,8 +30,6 @@ module React | |
, pureComponent | ||
, pureComponentWithDerivedState | ||
, statelessComponent | ||
, ReactClass | ||
, ReactRef | ||
, getProps | ||
, getState | ||
, setState | ||
|
@@ -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 | ||
|
||
|
@@ -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 -> | ||
|
@@ -339,7 +331,7 @@ class ReactPropFields (required :: # Type) (given :: # Type) | |
|
||
type ReservedReactPropFields r = | ||
( key :: String | ||
, ref :: SyntheticEventHandler (Nullable ReactRef) | ||
, ref :: Ref DOMRef | ||
| r | ||
) | ||
|
||
|
@@ -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 | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this all uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good for |
||
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 |
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) | ||
; | ||
}; |
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) |
There was a problem hiding this comment.
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 isForeign
. 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.There was a problem hiding this comment.
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 typeForwardRef
instead? I am not sure if there is a better way to hand this, but I am open to ideas.