Skip to content

Commit 12caae5

Browse files
committed
Add React.memo
1 parent 309f617 commit 12caae5

File tree

5 files changed

+46
-7
lines changed

5 files changed

+46
-7
lines changed

core/src/main/scala/japgolly/scalajs/react/React.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ object React {
3434
*/
3535
@inline def forwardRef = component.ReactForwardRef
3636

37+
/** Class components can bail out from rendering when their input props are the same using shouldComponentUpdate.
38+
* Now you can do the same with function components by wrapping them in React.memo.
39+
*
40+
* @since 1.4.0 / React 16.6.0
41+
*/
42+
def memo[P, CT[-p, +u] <: CtorType[p, u]](c: ScalaFnComponent[P, CT])
43+
(implicit r: Reusability[P],
44+
s: CtorType.Summoner[Box[P], c.ctor.ChildrenType])
45+
: GenericComponent[P, s.CT, JsComponent.Unmounted[Box[P], Null]] = {
46+
val r2 = implicitly[Reusability[Box[P]]]
47+
val c2 = Raw.React.memo(c.raw, r2.test)
48+
JsComponent.force[Box[P], c.ctor.ChildrenType, Null](c2)
49+
.cmapCtorProps[P](Box(_))
50+
}
51+
3752
/** StrictMode is a tool for highlighting potential problems in an application.
3853
* Like Fragment, StrictMode does not render any visible UI.
3954
* It activates additional checks and warnings for its descendants.

core/src/main/scala/japgolly/scalajs/react/Reusability.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package japgolly.scalajs.react
22

3-
import japgolly.scalajs.react.internal.{OptionLike, ReusabilityMacros}
3+
import japgolly.scalajs.react.internal.{Box, OptionLike, ReusabilityMacros}
44
import java.util.{Date, UUID}
55
import org.scalajs.dom.console
66
import scala.annotation.tailrec
@@ -239,6 +239,9 @@ object Reusability {
239239
implicit def set[A]: Reusability[Set[A]] =
240240
byRefOr_== // universal equality must hold for Sets
241241

242+
implicit def box[A: Reusability]: Reusability[Box[A]] =
243+
by(_.unbox)
244+
242245
// Generated by bin/gen-reusable
243246

244247
implicit def tuple2[A:Reusability, B:Reusability]: Reusability[(A,B)] =

core/src/main/scala/japgolly/scalajs/react/raw/React.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ trait React extends js.Object {
211211
final def `lazy`[P <: js.Object](f: js.Function0[js.Promise[LazyResult[P]]]): Lazy[P] = js.native
212212

213213
/** @since 16.6.0 */
214-
final def memo[P <: js.Object, A](f: js.Function1[P, A], areEqual: js.Function2[P, P, Boolean] = js.native): js.Function1[P, A] = js.native
214+
final def memo[P <: js.Object, A](f: js.Function1[P, A], areEqual: js.Function2[P, P, Boolean] = js.native): js.Object = js.native
215215

216216
final val version: String = js.native
217217

doc/changelog/1.4.0.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
For technical reasons (types, power, constraints) `React.lazy` is not added nor needed in Scala;
3434
use the new `AsyncCallback` instead.
3535
* The new `getDerivedStateFromError` has *not* been implemented, and cannot be until facebook/react#13986 is merged.
36+
* Add `React.memo` to add reusability to "functional" components
3637

3738
* `Reusability` & `Reusable` have been moved from the `extra` module, into `core`.
3839
1. It was required to implement the new `React.memo`
@@ -83,9 +84,5 @@ find . -type f -name '*.scala' -exec perl -pi -e '
8384

8485
# TODO
8586

86-
* `React.memo`
87-
* Add `React.memo`
88-
* Add `.memo` (?) to `JsFnComponent`
89-
* Test from `ScalaFnComponent`
9087
* SSR stuff
9188
* Rename `.runNow()` to `.unsafeRunNow()`?

test/src/test/scala/japgolly/scalajs/react/core/ScalaFnComponentTest.scala

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package japgolly.scalajs.react.core
22

33
import utest._
44
import japgolly.scalajs.react._
5+
import japgolly.scalajs.react.test.ReactTestUtils
56
import japgolly.scalajs.react.vdom.html_<^._
67
import japgolly.scalajs.react.test.TestUtil._
78

89
object ScalaFnComponentTest extends TestSuite {
910

1011
val IntProps = ScalaFnComponent[Int](i => <.code(s"$i² = ${i * i}"))
1112

12-
case class Add(x: Int, y: Int)
13+
final case class Add(x: Int, y: Int)
1314

1415
val CaseClassProps = ScalaFnComponent[Add] { a =>
1516
import a._
@@ -30,5 +31,28 @@ object ScalaFnComponentTest extends TestSuite {
3031
'withChildren - assertRender(WithChildren(3)(c1, c2), "<div>i=3<i>good</i>222</div>")
3132
'justChild - assertRender(JustChildren(c1), "<h4><i>good</i></h4>")
3233
'justChildren - assertRender(JustChildren(c1, c2), "<h4><i>good</i>222</h4>")
34+
35+
'memo - {
36+
var rendered = 0
37+
implicit def reusabilityAdd: Reusability[Add] = Reusability.by(_.x)
38+
val c = React.memo(ScalaFnComponent[Add] { _ =>
39+
rendered += 1
40+
<.br
41+
})
42+
val w = ScalaComponent.builder[Unit]("").initialState(Add(1, 1)).render_S(c(_)).build
43+
ReactTestUtils.withRenderedIntoDocument(w()) { m =>
44+
assert(rendered == 1)
45+
m.setState(Add(1, 2))
46+
assert(rendered == 1)
47+
m.setState(Add(2, 2))
48+
assert(rendered == 2)
49+
m.setState(Add(2, 3))
50+
assert(rendered == 2)
51+
m.setState(Add(2, 2))
52+
assert(rendered == 2)
53+
m.setState(Add(1, 2))
54+
assert(rendered == 3)
55+
}
56+
}
3357
}
3458
}

0 commit comments

Comments
 (0)