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
7 changes: 3 additions & 4 deletions src/React.purs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ module React

import Prelude

import Data.Nullable (Nullable)
import Effect (Effect)
import Effect.Exception (Error)
import Effect.Uncurried (EffectFn1)
Expand All @@ -72,7 +71,8 @@ import Type.Row (type (+))
import Unsafe.Coerce (unsafeCoerce)

import React.Hook (Hook)
import React.Types (ReactElement, ReactClass, Children, ReactRef)
import React.Ref (Ref, DOMRef)
import React.Types (ReactElement, ReactClass, Children)
import React.Types
( ReactClass
, ReactElement
Expand All @@ -81,7 +81,6 @@ import React.Types
, Children
, childrenToArray
, childrenCount
, ReactRef
) as Exports

-- | Name of a tag.
Expand Down Expand Up @@ -332,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
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
14 changes: 0 additions & 14 deletions src/React/Hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,12 @@ exports.useRef_ = function useRef_(initialValue) {
return result;
}

exports.getRef_ = function getRef_(ref) {
return ref.current;
}

exports.setRef_ = function setRef_(ref, value) {
ref.current = value;
}

exports.useImperativeMethods_ = function useImperativeMethods_(ref, imperativeMethods, inputs) {
var result = inputs ? React.useImperativeMethods(ref, imperativeMethods, inputs) : React.useImperativeMethods(ref, imperativeMethods);

return result;
};

exports.useMutationEffect_ = function useMutationEffect_(mutationEffect, inputs) {
var result = inputs ? React.useMutationEffect(mutationEffect, inputs) : React.useMutationEffect(mutationEffect);

return result;
};

exports.useLayoutEffect_ = function useLayoutEffect_(layoutEffect, inputs) {
var result = inputs ? React.useLayoutEffect(layoutEffect, inputs) : React.useLayoutEffect(layoutEffect);

Expand Down
81 changes: 10 additions & 71 deletions src/React/Hook.purs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,8 @@ module React.Hook
, useCallback
, useMemo
, useRef
, getRef
, setRef
, refToReactRef
, Ref
, useImperativeMethods
, imperativeMethodsInput
, ImperativeMethodsInput
, useMutationEffect
, mutationEffectInput
, MutationEffectInput
, useLayoutEffect
, layoutEffectInput
, LayoutEffectInput
) where

import Prelude
Expand All @@ -40,12 +29,12 @@ import Data.Nullable as Nullable
import Data.Tuple (Tuple(..))

import Effect (Effect)
import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)
import Effect.Uncurried (EffectFn1, runEffectFn1)

import Unsafe.Coerce (unsafeCoerce)

import React.Context (Context)
import React.Types (ReactRef)
import React.Ref (Ref)

useState
:: forall a
Expand Down Expand Up @@ -179,87 +168,37 @@ foreign import useMemo_
useRef :: forall a. Maybe a -> Hook (Ref a)
useRef = runFn1 useRef_ <<< Nullable.toNullable

refToReactRef :: forall a. Ref a -> ReactRef
refToReactRef = unsafeCoerce

getRef :: forall a. Ref a -> Effect (Maybe a)
getRef r = Nullable.toMaybe <$> runEffectFn1 getRef_ r

setRef :: forall a. Ref a -> Maybe a -> Effect Unit
setRef r = runEffectFn2 setRef_ r <<< Nullable.toNullable

foreign import data Ref :: Type -> Type

foreign import useRef_
:: forall a
. Fn1 (Nullable a)
(Hook (Ref a))

foreign import getRef_
:: forall a
. EffectFn1 (Ref a)
(Nullable a)

foreign import setRef_
:: forall a
. EffectFn2 (Ref a)
(Nullable a)
Unit

useImperativeMethods
:: forall r a
:: forall a
. Ref a
-> (Unit -> { | r })
-> Maybe (Array ImperativeMethodsInput)
-> (Unit -> a)
-> Maybe (Array HookInput)
-> Hook Unit
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure about this type. useImperativeMethods depends on forwardRef, which will pass in a second argument to your component. We don't expose this anywhere, so I don't know if Ref a is the correct type. I think it would actually be very hard to make use of this in even a remotely type-safe way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed that this may not be the best representation. I still need to work this one out a bit more. I am open to suggestions on this.

useImperativeMethods a k = runFn3 useImperativeMethods_ a k <<< Nullable.toNullable

imperativeMethodsInput :: forall a. a -> ImperativeMethodsInput
imperativeMethodsInput = unsafeCoerce

foreign import data ImperativeMethodsInput :: Type

foreign import useImperativeMethods_
:: forall r a
. Fn3 (Ref a)
(Unit -> { | r })
(Nullable (Array ImperativeMethodsInput))
(Hook Unit)

useMutationEffect
:: forall a
. Effect (Effect a)
-> Maybe (Array MutationEffectInput)
-> Hook Unit
useMutationEffect k = runFn2 useMutationEffect_ k <<< Nullable.toNullable

mutationEffectInput :: forall a. a -> MutationEffectInput
mutationEffectInput = unsafeCoerce

foreign import data MutationEffectInput :: Type

foreign import useMutationEffect_
:: forall a
. Fn2 (Effect (Effect a))
(Nullable (Array MutationEffectInput))
. Fn3 (Ref a)
(Unit -> a)
(Nullable (Array HookInput))
(Hook Unit)

useLayoutEffect
:: forall a
. Effect (Effect a)
-> Maybe (Array LayoutEffectInput)
-> Maybe (Array HookInput)
-> Hook Unit
useLayoutEffect k = runFn2 useLayoutEffect_ k <<< Nullable.toNullable

layoutEffectInput :: forall a. a -> LayoutEffectInput
layoutEffectInput = unsafeCoerce

foreign import data LayoutEffectInput :: Type

foreign import useLayoutEffect_
:: forall a
. Fn2 (Effect (Effect a))
(Nullable (Array LayoutEffectInput))
(Nullable (Array HookInput))
(Hook Unit)

foreign import data Hook :: Type -> Type
Expand Down
19 changes: 19 additions & 0 deletions src/React/Ref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

var React = require('react');

exports.createRef = function createRef() {
return React.createRef();
};

exports.forwardRef_ = function forwardRef(render) {
return React.forwardRef(render);
}

exports.getRef_ = function getRef_(ref) {
return ref.current;
}

exports.setRef_ = function setRef_(ref, value) {
ref.current = value;
}
54 changes: 54 additions & 0 deletions src/React/Ref.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module React.Ref
( Ref
, DOMRef
, createRef
, forwardRef
, getRef
, setRef
) where

import Prelude

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

import Effect (Effect)
import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)

import React.Types (ReactClass, ReactElement)

-- | Type for React refs.
foreign import data Ref :: Type -> Type

-- | Type for a Ref value that is a React component instance or a DOM element.
-- | This type is opaque, but you can use `Data.Foreign` and `DOM` to validate the underlying representation.
foreign import data DOMRef :: Type

foreign import createRef :: forall a. Effect (Ref a)

forwardRef :: forall props a. (props -> Ref a -> ReactElement) -> ReactClass props
forwardRef = forwardRef_ <<< mkFn2

foreign import forwardRef_
:: forall props a
. Fn2 props (Ref a) ReactElement
-> ReactClass props

getRef :: forall a. Ref a -> Effect (Maybe a)
getRef r = Nullable.toMaybe <$> runEffectFn1 getRef_ r

foreign import getRef_
:: forall a
. EffectFn1 (Ref a)
(Nullable a)

setRef :: forall a. Ref a -> Maybe a -> Effect Unit
setRef r = runEffectFn2 setRef_ r <<< Nullable.toNullable

foreign import setRef_
:: forall a
. EffectFn2 (Ref a)
(Nullable a)
Unit
5 changes: 0 additions & 5 deletions src/React/Types.purs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module React.Types
, Children
, childrenToArray
, childrenCount
, ReactRef
) where

import Prelude
Expand Down Expand Up @@ -56,7 +55,3 @@ foreign import childrenToArray :: Children -> Array ReactElement

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

-- | 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