From 96f0212bebd289c5ac209999bf0d02201a2725a5 Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Thu, 2 Jun 2022 19:38:18 +0200 Subject: [PATCH 01/11] Add useId --- src/React/Basic/Hooks.js | 2 ++ src/React/Basic/Hooks.purs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/React/Basic/Hooks.js b/src/React/Basic/Hooks.js index 01de237..d0303eb 100644 --- a/src/React/Basic/Hooks.js +++ b/src/React/Basic/Hooks.js @@ -73,6 +73,8 @@ export function useMemo_(eq, deps, computeA) { export const useDebugValue_ = React.useDebugValue; +export const useId_ = React.useId + export function unsafeSetDisplayName(displayName, component) { component.displayName = displayName; component.toString = () => displayName; diff --git a/src/React/Basic/Hooks.purs b/src/React/Basic/Hooks.purs index 5cea930..ef746e3 100644 --- a/src/React/Basic/Hooks.purs +++ b/src/React/Basic/Hooks.purs @@ -38,6 +38,8 @@ module React.Basic.Hooks , UseMemo , useDebugValue , UseDebugValue + , useId + , UseId , UnsafeReference(..) , displayName , module React.Basic.Hooks.Internal @@ -354,6 +356,10 @@ 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_ + newtype UnsafeReference a = UnsafeReference a @@ -478,3 +484,5 @@ foreign import useDebugValue_ :: a (a -> String) Unit + +foreign import useId_ :: Effect String \ No newline at end of file From 93b6fcab1b16f65f1c65806e732e8fcd08b5ba3e Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Thu, 2 Jun 2022 19:48:02 +0200 Subject: [PATCH 02/11] Add useTransition --- src/React/Basic/Hooks.js | 5 +++++ src/React/Basic/Hooks.purs | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/React/Basic/Hooks.js b/src/React/Basic/Hooks.js index d0303eb..e9254ab 100644 --- a/src/React/Basic/Hooks.js +++ b/src/React/Basic/Hooks.js @@ -75,6 +75,11 @@ export const useDebugValue_ = React.useDebugValue; export const useId_ = React.useId +export function useTransition_(tuple) { + const [isPending, startTransition] = React.useTransition() + return tuple(isPending, (t) => () => startTransition(t)); +} + export function unsafeSetDisplayName(displayName, component) { component.displayName = displayName; component.toString = () => displayName; diff --git a/src/React/Basic/Hooks.purs b/src/React/Basic/Hooks.purs index ef746e3..02e51a1 100644 --- a/src/React/Basic/Hooks.purs +++ b/src/React/Basic/Hooks.purs @@ -40,6 +40,8 @@ module React.Basic.Hooks , UseDebugValue , useId , UseId + , useTransition + , UseTransition , UnsafeReference(..) , displayName , module React.Basic.Hooks.Internal @@ -58,6 +60,7 @@ import Data.Tuple (Tuple(..)) import Data.Tuple.Nested (type (/\), (/\)) import Effect (Effect) import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, runEffectFn1, runEffectFn2, runEffectFn3) +import Foreign (Foreign) import Prelude (bind) as Prelude import Prim.Row (class Lacks) import React.Basic (JSX, ReactComponent, ReactContext, Ref, consumer, contextConsumer, contextProvider, createContext, element, elementKeyed, empty, keyed, fragment, provider) @@ -360,6 +363,11 @@ 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_ Tuple + newtype UnsafeReference a = UnsafeReference a @@ -485,4 +493,7 @@ foreign import useDebugValue_ :: (a -> String) Unit -foreign import useId_ :: Effect String \ No newline at end of file +foreign import useId_ :: Effect String + +foreign import useTransition_ + :: forall a b. EffectFn1 (a -> b -> Tuple a b) (Boolean /\ ((Effect Unit) -> Effect Unit)) \ No newline at end of file From 421ad199a48ba834bd2110b18432ab1ceb3f03b2 Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Thu, 2 Jun 2022 19:53:33 +0200 Subject: [PATCH 03/11] Add useDeferredValue --- src/React/Basic/Hooks.js | 2 ++ src/React/Basic/Hooks.purs | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/React/Basic/Hooks.js b/src/React/Basic/Hooks.js index e9254ab..f744f2f 100644 --- a/src/React/Basic/Hooks.js +++ b/src/React/Basic/Hooks.js @@ -80,6 +80,8 @@ export function useTransition_(tuple) { return tuple(isPending, (t) => () => startTransition(t)); } +export const useDeferredValue_ = React.useDeferredValue + export function unsafeSetDisplayName(displayName, component) { component.displayName = displayName; component.toString = () => displayName; diff --git a/src/React/Basic/Hooks.purs b/src/React/Basic/Hooks.purs index 02e51a1..addb654 100644 --- a/src/React/Basic/Hooks.purs +++ b/src/React/Basic/Hooks.purs @@ -42,6 +42,8 @@ module React.Basic.Hooks , UseId , useTransition , UseTransition + , useDeferredValue + , UseDeferredValue , UnsafeReference(..) , displayName , module React.Basic.Hooks.Internal @@ -60,7 +62,6 @@ import Data.Tuple (Tuple(..)) import Data.Tuple.Nested (type (/\), (/\)) import Effect (Effect) import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, runEffectFn1, runEffectFn2, runEffectFn3) -import Foreign (Foreign) import Prelude (bind) as Prelude import Prim.Row (class Lacks) import React.Basic (JSX, ReactComponent, ReactContext, Ref, consumer, contextConsumer, contextProvider, createContext, element, elementKeyed, empty, keyed, fragment, provider) @@ -368,6 +369,10 @@ useTransition :: Hook UseTransition (Boolean /\ ((Effect Unit) -> Effect Unit)) useTransition = unsafeHook $ runEffectFn1 useTransition_ Tuple +foreign import data UseDeferredValue :: Type -> Type -> Type +useDeferredValue :: forall a. a -> Hook (UseDeferredValue a) a +useDeferredValue a = unsafeHook $ runEffectFn1 useDeferredValue_ a + newtype UnsafeReference a = UnsafeReference a @@ -496,4 +501,6 @@ foreign import useDebugValue_ :: foreign import useId_ :: Effect String foreign import useTransition_ - :: forall a b. EffectFn1 (a -> b -> Tuple a b) (Boolean /\ ((Effect Unit) -> Effect Unit)) \ No newline at end of file + :: forall a b. EffectFn1 (a -> b -> Tuple a b) (Boolean /\ ((Effect Unit) -> Effect Unit)) + +foreign import useDeferredValue_ :: forall a. EffectFn1 a a \ No newline at end of file From 53a11e4e1905e579bb3e8bed3f50401302e0268f Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Thu, 2 Jun 2022 20:07:11 +0200 Subject: [PATCH 04/11] Add useSyncExternalStore --- src/React/Basic/Hooks.js | 5 ++++- src/React/Basic/Hooks.purs | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/React/Basic/Hooks.js b/src/React/Basic/Hooks.js index f744f2f..b732464 100644 --- a/src/React/Basic/Hooks.js +++ b/src/React/Basic/Hooks.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useSyncExternalStore } from "react"; const useEqCache = (eq, a) => { const memoRef = React.useRef(a); @@ -82,6 +82,9 @@ export function useTransition_(tuple) { 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; diff --git a/src/React/Basic/Hooks.purs b/src/React/Basic/Hooks.purs index addb654..2d64714 100644 --- a/src/React/Basic/Hooks.purs +++ b/src/React/Basic/Hooks.purs @@ -44,6 +44,9 @@ module React.Basic.Hooks , UseTransition , useDeferredValue , UseDeferredValue + , useSyncExternalStore + , useSyncExternalStore' + , UseSyncExternalStore , UnsafeReference(..) , displayName , module React.Basic.Hooks.Internal @@ -373,6 +376,23 @@ 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 Unit) + -> (Effect a) + -> (Effect a) + -> Hook (UseSyncExternalStore a) a +useSyncExternalStore subscribe getSnapshot getServerSnapshot = + unsafeHook $ + runEffectFn3 useSyncExternalStore3_ subscribe getSnapshot getServerSnapshot +useSyncExternalStore' :: forall a. + ((Effect Unit) -> Effect Unit) + -> (Effect a) + -> Hook (UseSyncExternalStore a) a +useSyncExternalStore' subscribe getSnapshot = + unsafeHook $ + runEffectFn2 useSyncExternalStore2_ subscribe getSnapshot + newtype UnsafeReference a = UnsafeReference a @@ -503,4 +523,15 @@ foreign import useId_ :: Effect String foreign import useTransition_ :: forall a b. EffectFn1 (a -> b -> Tuple a b) (Boolean /\ ((Effect Unit) -> Effect Unit)) -foreign import useDeferredValue_ :: forall a. EffectFn1 a a \ No newline at end of file +foreign import useDeferredValue_ :: forall a. EffectFn1 a a + +foreign import useSyncExternalStore2_ :: forall a. EffectFn2 + ((Effect Unit) -> Effect Unit) -- subscribe + (Effect a) -- getSnapshot + a + +foreign import useSyncExternalStore3_ :: forall a. EffectFn3 + ((Effect Unit) -> Effect Unit) -- subscribe + (Effect a) -- getSnapshot + (Effect a) -- getServerSnapshot + a \ No newline at end of file From 06a6db0d9dfb317e502bcf60acb15216dab19006 Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Thu, 2 Jun 2022 20:12:48 +0200 Subject: [PATCH 05/11] Add useInsertionEffect --- src/React/Basic/Hooks.js | 9 +++++++++ src/React/Basic/Hooks.purs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/React/Basic/Hooks.js b/src/React/Basic/Hooks.js index b732464..d0dbd45 100644 --- a/src/React/Basic/Hooks.js +++ b/src/React/Basic/Hooks.js @@ -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")) { diff --git a/src/React/Basic/Hooks.purs b/src/React/Basic/Hooks.purs index 2d64714..e9ff6ca 100644 --- a/src/React/Basic/Hooks.purs +++ b/src/React/Basic/Hooks.purs @@ -20,6 +20,10 @@ module React.Basic.Hooks , useLayoutEffectOnce , useLayoutEffectAlways , UseLayoutEffect + , useInsertionEffect + , useInsertionEffectOnce + , useInsertionEffectAlways + , UseInsertionEffect , Reducer , mkReducer , runReducer @@ -277,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 `useLayoutEffect` +--| 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) @@ -463,6 +487,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 From 5cbfaee66162f1ff27a120a3a2b5310f044769fc Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Fri, 3 Jun 2022 14:14:02 +0200 Subject: [PATCH 06/11] Fix signatures --- src/React/Basic/Hooks.js | 5 +++-- src/React/Basic/Hooks.purs | 20 ++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/React/Basic/Hooks.js b/src/React/Basic/Hooks.js index d0dbd45..1adb61e 100644 --- a/src/React/Basic/Hooks.js +++ b/src/React/Basic/Hooks.js @@ -85,8 +85,9 @@ export const useDebugValue_ = React.useDebugValue; export const useId_ = React.useId export function useTransition_(tuple) { - const [isPending, startTransition] = React.useTransition() - return tuple(isPending, (t) => () => startTransition(t)); + const [isPending, startTransitionImpl] = React.useTransition() + const startTransition = (update) => () => startTransitionImpl(update) + return tuple(isPending, startTransition); } export const useDeferredValue_ = React.useDeferredValue diff --git a/src/React/Basic/Hooks.purs b/src/React/Basic/Hooks.purs index e9ff6ca..d0ed6cf 100644 --- a/src/React/Basic/Hooks.purs +++ b/src/React/Basic/Hooks.purs @@ -394,7 +394,7 @@ useId = unsafeHook useId_ foreign import data UseTransition :: Type -> Type useTransition :: Hook UseTransition (Boolean /\ ((Effect Unit) -> Effect Unit)) -useTransition = unsafeHook $ runEffectFn1 useTransition_ Tuple +useTransition = unsafeHook $ runEffectFn1 useTransition_ (mkFn2 Tuple) foreign import data UseDeferredValue :: Type -> Type -> Type useDeferredValue :: forall a. a -> Hook (UseDeferredValue a) a @@ -402,20 +402,23 @@ useDeferredValue a = unsafeHook $ runEffectFn1 useDeferredValue_ a foreign import data UseSyncExternalStore :: Type -> Type -> Type useSyncExternalStore :: forall a. - ((Effect Unit) -> Effect Unit) + ((Effect Unit) -> Effect (Effect Unit)) -> (Effect a) -> (Effect a) -> Hook (UseSyncExternalStore a) a useSyncExternalStore subscribe getSnapshot getServerSnapshot = unsafeHook $ - runEffectFn3 useSyncExternalStore3_ subscribe getSnapshot getServerSnapshot + runEffectFn3 useSyncExternalStore3_ + (mkEffectFn1 subscribe) + getSnapshot + getServerSnapshot useSyncExternalStore' :: forall a. - ((Effect Unit) -> Effect Unit) + ((Effect Unit) -> Effect (Effect Unit)) -> (Effect a) -> Hook (UseSyncExternalStore a) a useSyncExternalStore' subscribe getSnapshot = unsafeHook $ - runEffectFn2 useSyncExternalStore2_ subscribe getSnapshot + runEffectFn2 useSyncExternalStore2_ (mkEffectFn1 subscribe) getSnapshot newtype UnsafeReference a = UnsafeReference a @@ -558,17 +561,18 @@ foreign import useDebugValue_ :: foreign import useId_ :: Effect String foreign import useTransition_ - :: forall a b. EffectFn1 (a -> b -> Tuple a b) (Boolean /\ ((Effect Unit) -> Effect Unit)) + :: 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 - ((Effect Unit) -> Effect Unit) -- subscribe + (EffectFn1 (Effect Unit) (Effect Unit)) -- subscribe (Effect a) -- getSnapshot a foreign import useSyncExternalStore3_ :: forall a. EffectFn3 - ((Effect Unit) -> Effect Unit) -- subscribe + (EffectFn1 (Effect Unit) (Effect Unit)) -- subscribe (Effect a) -- getSnapshot (Effect a) -- getServerSnapshot a \ No newline at end of file From 20411fca3b3c29d94aa01747c55b299bd7c97fcd Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Fri, 3 Jun 2022 14:15:15 +0200 Subject: [PATCH 07/11] Drop vendored Discovery --- packages.dhall | 4 ++-- spago.test.dhall | 7 +++++++ test/Discovery.js | 26 -------------------------- test/Discovery.purs | 21 --------------------- test/Main.purs | 2 +- 5 files changed, 10 insertions(+), 50 deletions(-) delete mode 100644 test/Discovery.js delete mode 100644 test/Discovery.purs diff --git a/packages.dhall b/packages.dhall index 01140c4..fa780b1 100644 --- a/packages.dhall +++ b/packages.dhall @@ -1,6 +1,6 @@ let upstream = - https://github.com/purescript/package-sets/releases/download/psc-0.15.0-20220523/packages.dhall - sha256:985f90fa68fd8b43b14c777d6ec2c161c4dd9009563b6f51685a54e4a26bf8ff + https://github.com/purescript/package-sets/releases/download/psc-0.15.2-20220531/packages.dhall + sha256:278d3608439187e51136251ebf12fabda62d41ceb4bec9769312a08b56f853e3 in upstream with react-testing-library = diff --git a/spago.test.dhall b/spago.test.dhall index d137a75..0f21444 100644 --- a/spago.test.dhall +++ b/spago.test.dhall @@ -6,5 +6,12 @@ in conf // { [ "react-testing-library" , "react-basic-dom" , "spec" + , "spec-discovery" + , "foreign-object" + , "web-dom" + , "arrays" + , "strings" + , "debug" + , "tailrec" ] } diff --git a/test/Discovery.js b/test/Discovery.js deleted file mode 100644 index 2001d2a..0000000 --- a/test/Discovery.js +++ /dev/null @@ -1,26 +0,0 @@ -import fs from "fs"; -import path from "path"; -import url from "url"; - -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); - -async function getMatchingModules(pattern) { - const directories = await fs.promises.readdir(path.join(__dirname, "..")); - const modules = await Promise.all( - directories - .filter((directory) => new RegExp(pattern).test(directory)) - .map(async (name) => { - const module = await import( - path.join(__dirname, "..", name, "index.js") - ); - return module && typeof module.spec !== "undefined" - ? module.spec - : null; - }) - ); - return modules.filter((x) => x); -} - -export function getSpecs(pattern) { - return () => getMatchingModules(pattern); -} diff --git a/test/Discovery.purs b/test/Discovery.purs deleted file mode 100644 index 3757c5e..0000000 --- a/test/Discovery.purs +++ /dev/null @@ -1,21 +0,0 @@ --- Vendored in because of --- https://github.com/purescript-spec/purescript-spec-discovery/issues/18 -module Test.Discovery (discover) where - -import Prelude - -import Control.Promise (Promise, toAffE) -import Data.Traversable (sequence_) -import Effect (Effect) -import Effect.Aff (Aff) -import Effect.Aff.Class (liftAff) -import Test.Spec (Spec) - -foreign import getSpecs ∷ - String -> - Effect (Promise (Array (Spec Unit))) - -discover ∷ String -> Aff (Spec Unit) -discover pattern = liftAff do - specsPromise <- toAffE $ getSpecs pattern - pure $ sequence_ specsPromise \ No newline at end of file diff --git a/test/Main.purs b/test/Main.purs index 3a07c63..dd1e96a 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -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') From c9fe43dd77f2c232977c45abf6c6ecc8fd845af3 Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Fri, 3 Jun 2022 14:15:19 +0200 Subject: [PATCH 08/11] Add tests for new hooks --- test/Spec/React18HooksSpec.purs | 143 ++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 test/Spec/React18HooksSpec.purs diff --git a/test/Spec/React18HooksSpec.purs b/test/Spec/React18HooksSpec.purs new file mode 100644 index 0000000..b896785 --- /dev/null +++ b/test/Spec/React18HooksSpec.purs @@ -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 + 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 (300.0 # Milliseconds) + spanElem `textContentShouldEqual` "2" + + 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 } \ No newline at end of file From 410f83ed52cd9a8dd26cf8a20fb86336b3351c48 Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Fri, 3 Jun 2022 14:17:25 +0200 Subject: [PATCH 09/11] Fix docstring --- src/React/Basic/Hooks.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/React/Basic/Hooks.purs b/src/React/Basic/Hooks.purs index d0ed6cf..64537d5 100644 --- a/src/React/Basic/Hooks.purs +++ b/src/React/Basic/Hooks.purs @@ -294,7 +294,7 @@ useInsertionEffect deps effect = unsafeHook (runEffectFn3 useInsertionEffect_ (m 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 `useLayoutEffect` +--| 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) From 9551c3a42e8d035d6a4012a47eddf5104a81d3f8 Mon Sep 17 00:00:00 2001 From: Mark Eibes Date: Fri, 3 Jun 2022 14:27:26 +0200 Subject: [PATCH 10/11] Make test less flaky --- test/Spec/React18HooksSpec.purs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Spec/React18HooksSpec.purs b/test/Spec/React18HooksSpec.purs index b896785..31ae9bc 100644 --- a/test/Spec/React18HooksSpec.purs +++ b/test/Spec/React18HooksSpec.purs @@ -56,8 +56,8 @@ spec = { findByTestId } <- renderComponent useSyncExternalStore {} spanElem <- findByTestId "span" spanElem `textContentShouldEqual` "0" - delay (300.0 # Milliseconds) - spanElem `textContentShouldEqual` "2" + delay (350.0 # Milliseconds) + spanElem `textContentShouldEqual` "3" it "useInsertionEffect works" \{ useInsertionEffect } -> do { findByText } <- renderComponent useInsertionEffect {} From 736cba58cf3a271ec187697a5d13d14fc34b1087 Mon Sep 17 00:00:00 2001 From: Madeline Trotter <715921+megamaddu@users.noreply.github.com> Date: Fri, 3 Jun 2022 23:41:02 -0700 Subject: [PATCH 11/11] remove redundant import --- src/React/Basic/Hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/React/Basic/Hooks.js b/src/React/Basic/Hooks.js index 1adb61e..255e7d4 100644 --- a/src/React/Basic/Hooks.js +++ b/src/React/Basic/Hooks.js @@ -1,4 +1,4 @@ -import React, { useSyncExternalStore } from "react"; +import React from "react"; const useEqCache = (eq, a) => { const memoRef = React.useRef(a);