Skip to content

New hooks #66

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

Merged
merged 12 commits into from
Jun 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages.dhall
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
let upstream =
https://github.com/purescript/package-sets/releases/download/psc-0.15.0-20220527/packages.dhall
sha256:15dd8041480502850e4043ea2977ed22d6ab3fc24d565211acde6f8c5152a799
https://github.com/purescript/package-sets/releases/download/psc-0.15.2-20220531/packages.dhall
sha256:278d3608439187e51136251ebf12fabda62d41ceb4bec9769312a08b56f853e3

in upstream
with react-testing-library =
Expand Down
7 changes: 7 additions & 0 deletions spago.test.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,12 @@ in conf // {
[ "react-testing-library"
, "react-basic-dom"
, "spec"
, "spec-discovery"
, "foreign-object"
, "web-dom"
, "arrays"
, "strings"
, "debug"
, "tailrec"
]
}
22 changes: 22 additions & 0 deletions src/React/Basic/Hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ export function useLayoutEffectAlways_(effect) {
return React.useLayoutEffect(effect);
}

export function useInsertionEffect_(eq, deps, effect) {
const memoizedKey = useEqCache(eq, deps);
React.useInsertionEffect(effect, [memoizedKey]);
}

export function useInsertionEffectAlways_(effect) {
React.useInsertionEffect(effect);
}

export function useReducer_(tuple, reducer, initialState) {
const [state, dispatch] = React.useReducer(reducer, initialState);
if (!dispatch.hasOwnProperty("$$reactBasicHooks$$cachedDispatch")) {
Expand Down Expand Up @@ -73,6 +82,19 @@ export function useMemo_(eq, deps, computeA) {

export const useDebugValue_ = React.useDebugValue;

export const useId_ = React.useId

export function useTransition_(tuple) {
const [isPending, startTransitionImpl] = React.useTransition()
const startTransition = (update) => () => startTransitionImpl(update)
return tuple(isPending, startTransition);
}

export const useDeferredValue_ = React.useDeferredValue

export const useSyncExternalStore2_ = React.useSyncExternalStore
export const useSyncExternalStore3_ = React.useSyncExternalStore

export function unsafeSetDisplayName(displayName, component) {
component.displayName = displayName;
component.toString = () => displayName;
Expand Down
98 changes: 98 additions & 0 deletions src/React/Basic/Hooks.purs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ module React.Basic.Hooks
, useLayoutEffectOnce
, useLayoutEffectAlways
, UseLayoutEffect
, useInsertionEffect
, useInsertionEffectOnce
, useInsertionEffectAlways
, UseInsertionEffect
, Reducer
, mkReducer
, runReducer
Expand All @@ -38,6 +42,15 @@ module React.Basic.Hooks
, UseMemo
, useDebugValue
, UseDebugValue
, useId
, UseId
, useTransition
, UseTransition
, useDeferredValue
, UseDeferredValue
, useSyncExternalStore
, useSyncExternalStore'
, UseSyncExternalStore
, UnsafeReference(..)
, displayName
, module React.Basic.Hooks.Internal
Expand Down Expand Up @@ -268,6 +281,26 @@ useLayoutEffectAlways effect = unsafeHook (runEffectFn1 useLayoutEffectAlways_ e

foreign import data UseLayoutEffect :: Type -> Type -> Type

useInsertionEffect ::
forall deps.
Eq deps =>
deps ->
Effect (Effect Unit) ->
Hook (UseInsertionEffect deps) Unit
useInsertionEffect deps effect = unsafeHook (runEffectFn3 useInsertionEffect_ (mkFn2 eq) deps effect)

--| Like `useInsertionEffect`, but the effect is only performed a single time per component
--| instance. Prefer `useInsertionEffect` with a proper dependency list whenever possible!
useInsertionEffectOnce :: Effect (Effect Unit) -> Hook (UseInsertionEffect Unit) Unit
useInsertionEffectOnce effect = unsafeHook (runEffectFn3 useInsertionEffect_ (mkFn2 \_ _ -> true) unit effect)

--| Like `useInsertionEffect`, but the effect is performed on every render. Prefer `useInsertionEffect`
--| with a proper dependency list whenever possible!
useInsertionEffectAlways :: Effect (Effect Unit) -> Hook (UseInsertionEffect Unit) Unit
useInsertionEffectAlways effect = unsafeHook (runEffectFn1 useInsertionEffectAlways_ effect)

foreign import data UseInsertionEffect :: Type -> Type -> Type

newtype Reducer state action
= Reducer (Fn2 state action state)

Expand Down Expand Up @@ -354,6 +387,39 @@ useDebugValue debugValue display = unsafeHook (runEffectFn2 useDebugValue_ debug

foreign import data UseDebugValue :: Type -> Type -> Type

foreign import data UseId :: Type -> Type
useId :: Hook UseId String
useId = unsafeHook useId_

foreign import data UseTransition :: Type -> Type
useTransition ::
Hook UseTransition (Boolean /\ ((Effect Unit) -> Effect Unit))
useTransition = unsafeHook $ runEffectFn1 useTransition_ (mkFn2 Tuple)

foreign import data UseDeferredValue :: Type -> Type -> Type
useDeferredValue :: forall a. a -> Hook (UseDeferredValue a) a
useDeferredValue a = unsafeHook $ runEffectFn1 useDeferredValue_ a

foreign import data UseSyncExternalStore :: Type -> Type -> Type
useSyncExternalStore :: forall a.
((Effect Unit) -> Effect (Effect Unit))
-> (Effect a)
-> (Effect a)
-> Hook (UseSyncExternalStore a) a
useSyncExternalStore subscribe getSnapshot getServerSnapshot =
unsafeHook $
runEffectFn3 useSyncExternalStore3_
(mkEffectFn1 subscribe)
getSnapshot
getServerSnapshot
useSyncExternalStore' :: forall a.
((Effect Unit) -> Effect (Effect Unit))
-> (Effect a)
-> Hook (UseSyncExternalStore a) a
useSyncExternalStore' subscribe getSnapshot =
unsafeHook $
runEffectFn2 useSyncExternalStore2_ (mkEffectFn1 subscribe) getSnapshot

newtype UnsafeReference a
= UnsafeReference a

Expand Down Expand Up @@ -424,6 +490,19 @@ foreign import useLayoutEffectAlways_ ::
(Effect (Effect Unit))
Unit

foreign import useInsertionEffect_ ::
forall deps.
EffectFn3
(Fn2 deps deps Boolean)
deps
(Effect (Effect Unit))
Unit

foreign import useInsertionEffectAlways_ ::
EffectFn1
(Effect (Effect Unit))
Unit

foreign import useReducer_ ::
forall state action.
EffectFn3
Expand Down Expand Up @@ -478,3 +557,22 @@ foreign import useDebugValue_ ::
a
(a -> String)
Unit

foreign import useId_ :: Effect String

foreign import useTransition_
:: forall a b. EffectFn1 (Fn2 a b (a /\ b))
(Boolean /\ ((Effect Unit) -> Effect Unit))

foreign import useDeferredValue_ :: forall a. EffectFn1 a a

foreign import useSyncExternalStore2_ :: forall a. EffectFn2
(EffectFn1 (Effect Unit) (Effect Unit)) -- subscribe
(Effect a) -- getSnapshot
a

foreign import useSyncExternalStore3_ :: forall a. EffectFn3
(EffectFn1 (Effect Unit) (Effect Unit)) -- subscribe
(Effect a) -- getSnapshot
(Effect a) -- getServerSnapshot
a
26 changes: 0 additions & 26 deletions test/Discovery.js

This file was deleted.

21 changes: 0 additions & 21 deletions test/Discovery.purs

This file was deleted.

2 changes: 1 addition & 1 deletion test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Data.Maybe (Maybe(..))
import Data.Time.Duration (Seconds(..), fromDuration)
import Effect (Effect)
import Effect.Aff (delay, launchAff_)
import Test.Discovery (discover)
import Test.Spec.Discovery (discover)
import Test.Spec.Reporter (consoleReporter)
import Test.Spec.Runner (defaultConfig, runSpec')

Expand Down
143 changes: 143 additions & 0 deletions test/Spec/React18HooksSpec.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
module Test.Spec.React18HooksSpec where

import Prelude

import Control.Monad.Rec.Class (forever)
import Data.Array as Array
import Data.Foldable (for_, traverse_)
import Data.Maybe (fromMaybe)
import Data.Monoid (guard, power)
import Data.String as String
import Data.Tuple.Nested ((/\))
import Effect.Aff (Milliseconds(..), apathize, delay, launchAff_)
import Effect.Class (liftEffect)
import Effect.Ref as Ref
import Foreign.Object as Object
import React.Basic (fragment)
import React.Basic.DOM as R
import React.Basic.DOM.Events (targetValue)
import React.Basic.Events (handler, handler_)
import React.Basic.Hooks (reactComponent)
import React.Basic.Hooks as Hooks
import React.TestingLibrary (cleanup, fireEventClick, renderComponent, typeText)
import Test.Spec (Spec, after_, before, describe, it)
import Test.Spec.Assertions (shouldNotEqual)
import Test.Spec.Assertions.DOM (textContentShouldEqual)
import Web.DOM.Element (getAttribute)
import Web.HTML.HTMLElement as HTMLElement

spec ∷ Spec Unit
spec =
after_ cleanup do
before setup do
describe "React 18 hooks" do
it "useId works" \{ useId } -> do
{ findByTestId } <- renderComponent useId {}
elem <- findByTestId "use-id"
idʔ <- getAttribute "id" (HTMLElement.toElement elem) # liftEffect
Copy link
Member

Choose a reason for hiding this comment

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

😮 I never knew you could use ? in identifiers........

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, it's a habit, I can remove it. It's not ? but ʔ which is from a glottal stop

Copy link
Member Author

Choose a reason for hiding this comment

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

  "Almost question mark": {
		"scope": "purescript",
		"prefix": "?",
		"body": [
      "ʔ"
		],
		"description": "Append a ʔ to a variable name which is an allowed identifier"
  },

I have that in a snippets file so I can do question mark tab to get it.

let id = idʔ # fromMaybe ""
id `shouldNotEqual` ""
elem `textContentShouldEqual` id

it "useTransition works" \{ useTransition } -> do
{ findByText } <- renderComponent useTransition {}
elem <- findByText "0"
fireEventClick elem
elem `textContentShouldEqual` "1"

it "useDeferredValue hopefully works" \{ useDeferredValue } -> do
{ findByTestId } <- renderComponent useDeferredValue {}
spanElem <- findByTestId "span"
spanElem `textContentShouldEqual` "0"
findByTestId "input" >>= typeText (power "text" 100)
spanElem `textContentShouldEqual` "400"

it "useSyncExternalStore" \{ useSyncExternalStore } -> do
{ findByTestId } <- renderComponent useSyncExternalStore {}
spanElem <- findByTestId "span"
spanElem `textContentShouldEqual` "0"
delay (350.0 # Milliseconds)
spanElem `textContentShouldEqual` "3"

it "useInsertionEffect works" \{ useInsertionEffect } -> do
{ findByText } <- renderComponent useInsertionEffect {}
void $ findByText "insertion-done"

where
setup = liftEffect ado

useId <-
reactComponent "UseIDExample" \(_ :: {}) -> Hooks.do
id <- Hooks.useId
pure $ R.div
{ id
, _data: Object.singleton "testid" "use-id"
, children: [ R.text id ]
}

useTransition <-
reactComponent "UseTransitionExample" \(_ :: {}) -> Hooks.do
isPending /\ startTransition <- Hooks.useTransition
count /\ setCount <- Hooks.useState 0
let handleClick = startTransition do setCount (_ + 1)
pure $ R.div
{ children:
[ guard isPending (R.text "Pending")
, R.button
{ onClick: handler_ handleClick
, children: [ R.text (show count) ]
}
]
}

useDeferredValue <-
reactComponent "UseDeferredValueExample" \(_ :: {}) -> Hooks.do
text /\ setText <- Hooks.useState' ""
textLength <- Hooks.useDeferredValue (String.length text)
pure $ fragment
[ R.input
{ onChange: handler targetValue (traverse_ setText)
, _data: Object.singleton "testid" "input"
}
, R.span
{ _data: Object.singleton "testid" "span"
, children: [ R.text (show textLength) ]
}
]

useInsertionEffect <-
reactComponent "UseInsertionEffectExample" \(_ :: {}) -> Hooks.do
text /\ setText <- Hooks.useState' "waiting"
Hooks.useInsertionEffect unit do
setText "insertion-done"
mempty
pure $ R.span_ [ R.text text ]

useSyncExternalStore <- do
{ subscribe, getSnapshot, getServerSnapshot } <- do
subscribersRef <- Ref.new []
intRef <- Ref.new 0
-- Update the intRef every 100ms.
launchAff_ $ apathize $ forever do
delay (100.0 # Milliseconds)
intRef # Ref.modify_ (_ + 1) # liftEffect
subscribers <- subscribersRef # Ref.read # liftEffect
liftEffect $ for_ subscribers identity

pure
{ subscribe: \callback -> do
subscribersRef # Ref.modify_ (Array.cons callback)
pure $
subscribersRef # Ref.modify_ (Array.drop 1)
, getSnapshot: Ref.read intRef
, getServerSnapshot: Ref.read intRef
}

reactComponent "UseSyncExternalStoreExample" \(_ :: {}) -> Hooks.do
number <- Hooks.useSyncExternalStore
subscribe
getSnapshot
getServerSnapshot
pure $ R.span { _data: Object.singleton "testid" "span", children: [ R.text (show number) ] }

in { useId, useTransition, useDeferredValue, useInsertionEffect, useSyncExternalStore }
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy