From 4889cbb1eeb8f6cfb6afc87074f8773631730453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Piaggio?= Date: Wed, 25 Dec 2024 13:21:09 -0300 Subject: [PATCH 1/2] add useReused hook --- .../japgolly/scalajs/react/hooks/extra.scala | 25 +++++++++++++------ .../scalajs/react/core/HooksTest.scala | 24 +++++++++++++++++- project/build.properties | 1 + 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 project/build.properties diff --git a/library/coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/extra.scala b/library/coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/extra.scala index a905c4175..385a5c0a7 100644 --- a/library/coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/extra.scala +++ b/library/coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/extra.scala @@ -54,18 +54,27 @@ trait extra { HookResult(UseRef.unsafeCreateToJsComponent(a)) /** - * Returns a stateful value, and a function to update it. - * - * During the initial render, the returned state is the same as the value passed as the first - * argument (initialState). - * - * During subsequent re-renders, the first value returned by useState will always be the most recent - * state after applying updates. - */ + * Returns a stateful value, and a function to update it. + * + * During the initial render, the returned state is the same as the value passed as the first + * argument (initialState). + * + * During subsequent re-renders, the first value returned by useState will always be the most recent + * state after applying updates. + */ @inline final def useStateWithReuse[S: ClassTag: Reusability]( initialState: => S ): HookResult[UseStateWithReuse[S]] = HookResult(UseStateWithReuse.unsafeCreate(initialState)) + /** + * Given a reusable value, returns the original value that is being reused whenver reusability + * applies, together with a revisition a number that increments only when the value isn't reused. + * + * Useful for using `Reusability` logic in facades to JS hooks that accept dependencies: you can + * pass the revision as a dependency to the JS hook. + */ + @inline final def useReused[D: Reusability](deps: => D): HookResult[(D, Int)] = + CustomHook.reusableDeps[D].toHookResult(() => deps) } diff --git a/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala b/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala index 3e028c547..462f55934 100644 --- a/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala +++ b/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala @@ -2054,7 +2054,28 @@ object HooksTest extends TestSuite { } } - def testFromFunction() = { + private def testUseReused(): Unit = { + val reuseIntByRounding: Reusability[Int] = Reusability.by(_ / 2) + + val comp = ScalaFnComponent[Unit] { _ => + for { + count <- useState(0) + (stable, rev) <- useReused(count.value)(reuseIntByRounding) + } yield + <.div( + <.div(s"count=${count.value}, stable=$stable, rev=$rev"), + <.button(^.onClick --> count.modState(_ + 1)) + ) + } + + test(comp()) { (t) => + t.assertText("count=0, stable=0, rev=1") + t.clickButton(1); t.assertText("count=1, stable=0, rev=1") + t.clickButton(1); t.assertText("count=2, stable=2, rev=2") + } + } + + private def testFromFunction() = { val jsHook1: js.Function1[Int, Int] = _ + 1 val jsHook2: js.Function2[Int, Int, String] = (a: Int, b: Int) => (a + b).toString @@ -2210,6 +2231,7 @@ object HooksTest extends TestSuite { "renderWithReuse (monadic alternative using Render.memo)" - { "main" - testMonadicRenderWithReuse() } + "useReused" - testUseReused() "fromFunction" - testFromFunction() } } diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 000000000..e88a0d817 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.10.6 From a1853a4d5f90e53aad8511d69d734c900087305a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Piaggio?= Date: Wed, 25 Dec 2024 13:43:56 -0300 Subject: [PATCH 2/2] fix test to run in both scala versions --- .../japgolly/scalajs/react/core/HooksTest.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala b/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala index 462f55934..8279e279c 100644 --- a/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala +++ b/library/tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala @@ -2055,12 +2055,13 @@ object HooksTest extends TestSuite { } private def testUseReused(): Unit = { - val reuseIntByRounding: Reusability[Int] = Reusability.by(_ / 2) + implicit val reusePIByRounding: Reusability[PI] = Reusability.by(_.pi / 2) val comp = ScalaFnComponent[Unit] { _ => for { - count <- useState(0) - (stable, rev) <- useReused(count.value)(reuseIntByRounding) + count <- useState(PI(0)) + reused <- useReused(count.value) + (stable, rev) = reused } yield <.div( <.div(s"count=${count.value}, stable=$stable, rev=$rev"), @@ -2069,9 +2070,9 @@ object HooksTest extends TestSuite { } test(comp()) { (t) => - t.assertText("count=0, stable=0, rev=1") - t.clickButton(1); t.assertText("count=1, stable=0, rev=1") - t.clickButton(1); t.assertText("count=2, stable=2, rev=2") + t.assertText("count=PI(0), stable=PI(0), rev=1") + t.clickButton(1); t.assertText("count=PI(1), stable=PI(0), rev=1") + t.clickButton(1); t.assertText("count=PI(2), stable=PI(2), rev=2") } }