Skip to content

Commit 3a814b7

Browse files
committed
done
1 parent 45efd23 commit 3a814b7

File tree

7 files changed

+292
-15
lines changed

7 files changed

+292
-15
lines changed

coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/Api.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,8 +681,8 @@ object Api {
681681
trait PrimaryWithRender[P, C <: Children, Ctx, _Step <: AbstractStep] extends Primary[Ctx, _Step] {
682682
def render(f: Ctx => VdomNode)(implicit s: CtorType.Summoner[Box[P], C]): Component[P, s.CT]
683683

684-
final def renderReusable(f: Ctx => Reusable[VdomNode])(implicit s: CtorType.Summoner[Box[P], C]): Component[P, s.CT] =
685-
renderWithReuseBy(f)(_.value)
684+
final def renderReusable[A](f: Ctx => Reusable[A])(implicit s: CtorType.Summoner[Box[P], C], v: A => VdomNode): Component[P, s.CT] =
685+
renderWithReuseBy(f(_).map(v))(_.value)
686686

687687
final def renderWithReuse(f: Ctx => VdomNode)(implicit s: CtorType.Summoner[Box[P], C], r: Reusability[Ctx]): Component[P, s.CT] =
688688
renderWithReuseBy(identityFn[Ctx])(f)
@@ -694,7 +694,7 @@ object Api {
694694
final def render(f: CtxFn[VdomNode])(implicit step: Step, s: CtorType.Summoner[Box[P], C]): Component[P, s.CT] =
695695
render(step.squash(f)(_))
696696

697-
final def renderReusable(f: CtxFn[Reusable[VdomNode]])(implicit step: Step, s: CtorType.Summoner[Box[P], C]): Component[P, s.CT] =
697+
final def renderReusable[A](f: CtxFn[Reusable[A]])(implicit step: Step, s: CtorType.Summoner[Box[P], C], v: A => VdomNode): Component[P, s.CT] =
698698
renderReusable(step.squash(f)(_))
699699

700700
final def renderWithReuse(f: CtxFn[VdomNode])(implicit step: Step, s: CtorType.Summoner[Box[P], C], r: Reusability[Ctx]): Component[P, s.CT] =

coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/CustomHook.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,10 @@ object CustomHook {
202202
val inc: Int => Int = _ + 1
203203
CustomHook[Unit]
204204
.useState(0)
205-
.buildReturning(_.hook1.modState.map(_(inc)))
205+
.buildReturning($ => {
206+
val s = $.hook1
207+
Reusable.implicitly(s.value).withLazyValue(s.modState.map(_(inc)).value)
208+
})
206209
}
207210

208211
}

coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/HookCtx.scala

Lines changed: 196 additions & 1 deletion
Large diffs are not rendered by default.

coreGeneric/src/main/scala/japgolly/scalajs/react/hooks/Hooks.scala

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ object Hooks {
134134
val originalDispatch = Reusable.byRef(originalResult._2)
135135
UseReducer(originalResult, originalDispatch)
136136
}
137+
138+
implicit def reusability[S, A](implicit S: Reusability[S]): Reusability[UseReducer[S, A]] = {
139+
val r = implicitly[Reusability[Reusable[facade.React.UseReducerDispatch[_]]]]
140+
Reusability((x, y) => S.test(x.value, y.value) && r.test(x.originalDispatch, y.originalDispatch))
141+
}
137142
}
138143

139144
final case class UseReducer[S, A](raw: facade.React.UseReducer[S, A], originalDispatch: Reusable[facade.React.UseReducerDispatch[_]]) {
@@ -239,13 +244,18 @@ object Hooks {
239244
}
240245
}
241246

242-
def UseStateF[F[_], S, O](r: facade.React.UseState[S], oss: Reusable[facade.React.UseStateSetter[O]])(implicit f: Sync[F]): UseStateF[F, S] =
243-
new UseStateF[F, S] {
244-
override protected[hooks] implicit def F = f
245-
override val raw = r
246-
override type OriginalState = O
247-
override val originalSetState = oss
248-
}
247+
object UseStateF {
248+
def apply[F[_], S, O](r: facade.React.UseState[S], oss: Reusable[facade.React.UseStateSetter[O]])(implicit f: Sync[F]): UseStateF[F, S] =
249+
new UseStateF[F, S] {
250+
override protected[hooks] implicit def F = f
251+
override val raw = r
252+
override type OriginalState = O
253+
override val originalSetState = oss
254+
}
255+
256+
implicit def reusability[F[_], S: Reusability]: Reusability[UseStateF[F, S]] =
257+
Reusability.by(_.value)
258+
}
249259

250260
trait UseStateF[F[_], S] { self =>
251261
protected[hooks] implicit def F: Sync[F]
@@ -351,6 +361,11 @@ object Hooks {
351361

352362
type UseStateWithReuse[S] = UseStateWithReuseF[D.Sync, S]
353363

364+
implicit def reusabilityUseStateWithReuseF[F[_], S]: Reusability[UseStateWithReuseF[F, S]] = {
365+
val r = implicitly[Reusability[Reusable[Reusability[S]]]]
366+
Reusability((x, y) => r.test(x.reusability, y.reusability) && x.reusability.value.test(x.value, y.value))
367+
}
368+
354369
final case class UseStateWithReuseF[F[_], S: ClassTag](withoutReuse: UseStateF[F, S], reusability: Reusable[Reusability[S]]) {
355370

356371
def withEffect[G[_]](implicit G: Sync[G]): UseStateWithReuseF[G, S] =

doc/HOOKS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* [Quickstart](HOOKS.md#quickstart)
55
* [React hooks in scalajs-react](HOOKS.md#react-hooks-in-scalajs-react)
66
* [New hooks provided by scalajs-react](HOOKS.md#new-hooks-provided-by-scalajs-react)
7+
* [`shouldComponentUpdate`](HOOKS.md#shouldcomponentupdate)
78
* [Hook chaining / dependencies / results](HOOKS.md#hook-chaining--dependencies--results)
89
* [Hooks and PropsChildren](HOOKS.md#hooks-and-propschildren)
910
* [Custom hooks](HOOKS.md#custom-hooks)
@@ -202,6 +203,15 @@ object Example {
202203
| `.useStateSnapshotWithReuse(initialState)` <br> *(Requires import japgolly.scalajs.react.extra._)* | Same as `.useState` except you get a `StateSnapshot` (which accepts callbacks on set updates) with state `Reusability`. |
203204
| `.useStateWithReuse(initialState)` | Conceptually `useState` + `shouldComponentUpdate`. Same as `useState` except that updates are dropped according to `Reusability`. |
204205

206+
# `shouldComponentUpdate`
207+
208+
Instead of calling `render`, you can call one of the following to get `shouldComponentUpdate`
209+
behaviour just like classes have.
210+
211+
* `renderWithReuse(f: Ctx => VdomNode)(implicit r: Reusability[Ctx])`
212+
* `renderWithReuseBy[A: Reusability](reusableInputs: Ctx => A)(f: A => VdomNode)`
213+
* `renderReusable(f: Ctx => Reusable[VdomNode])`
214+
205215
# Hook chaining / dependencies / results
206216

207217
Sometimes hooks are initialised using props and/or the output of other hooks,

project/GenHooks.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ object GenHooks {
4343
val _s = (1 to n).map(_ => '_').mkString(", ")
4444
val preHns = (1 until n).map("H" + _).mkString(", ")
4545
val Hns = (1 to n).map("H" + _).mkString(", ")
46+
val RHns = (1 to n).map(n => s"H$n: Reusability[H$n]").mkString(", ")
47+
val RHnTests = (1 to n).map(n => s"H$n.test(x.hook$n, y.hook$n)").mkString(" && ")
4648
val coHns = (1 to n).map("+H" + _).mkString(", ")
4749
val hookParams = (1 to n).map(i => s"hook$i: H$i").mkString(", ")
4850
val hookArgs = (1 to n).map(i => s"hook$i").mkString(", ")
@@ -67,13 +69,19 @@ object GenHooks {
6769
| override def toString = s"HookCtx.withInput(\\n input = !input$ctxToStr)"
6870
| def apply$n[A](f: (I, $Hns) => A): A = f(input, $hookArgs)
6971
| }
72+
|
73+
| implicit def reusabilityI$n[I, $Hns](implicit I: Reusability[I], $RHns): Reusability[I$n[I, $Hns]] =
74+
| Reusability((x, y) => I.test(x.input, y.input) && $RHnTests)
7075
|""".stripMargin.replace('!', '$')
7176

7277
hookCtxsP +=
7378
s""" class P$n[+P, $coHns](props: P, $ctxParams) extends P${n-1}(props$ctxSuperArgs) {
7479
| override def toString = s"HookCtx(\\n props = !props$ctxToStr)"
7580
| def apply$n[A](f: (P, $Hns) => A): A = f(props, $hookArgs)
7681
| }
82+
|
83+
| implicit def reusabilityP$n[P, $Hns](implicit P: Reusability[P], $RHns): Reusability[P$n[P, $Hns]] =
84+
| Reusability((x, y) => P.test(x.props, y.props) && $RHnTests)
7785
|""".stripMargin.replace('!', '$')
7886

7987
if (n <= 20) {
@@ -84,6 +92,9 @@ object GenHooks {
8492
| override def toString = s"HookCtx.withChildren(\\n props = !props,\\n propsChildren = !propsChildren$ctxToStr)"
8593
| def apply$n[A](f: (P, PropsChildren, $Hns) => A): A = f(props, propsChildren, $hookArgs)
8694
| }
95+
|
96+
| implicit def reusabilityPC$n[P, $Hns](implicit P: Reusability[P], PC: Reusability[PropsChildren], $RHns): Reusability[PC$n[P, $Hns]] =
97+
| Reusability((x, y) => P.test(x.props, y.props) && PC.test(x.propsChildren, y.propsChildren) && $RHnTests)
8798
|""".stripMargin.replace('!', '$')
8899
}
89100
}
@@ -198,14 +209,17 @@ object GenHooks {
198209
save("HookCtx.scala")(
199210
s"""$header
200211
|
201-
|import japgolly.scalajs.react.PropsChildren
212+
|import japgolly.scalajs.react.{PropsChildren, Reusability}
202213
|
203214
|object HookCtx {
204215
|
205216
|${hookCtxCtorsP.result().mkString("\n\n")}
206217
|
207218
| abstract class P0[+P](final val props: P)
208219
|
220+
| implicit def reusabilityP0[P](implicit P: Reusability[P]): Reusability[P0[P]] =
221+
| Reusability.by(_.props)
222+
|
209223
|${hookCtxsP.result().mkString("\n")}
210224
| // ===================================================================================================================
211225
|
@@ -219,6 +233,9 @@ object GenHooks {
219233
|
220234
| class PC0[+P](props: P, final val propsChildren: PropsChildren) extends P0(props)
221235
|
236+
| implicit def reusabilityPC0[P](implicit P: Reusability[P], PC: Reusability[PropsChildren]): Reusability[PC0[P]] =
237+
| Reusability((x, y) => P.test(x.props, y.props) && PC.test(x.propsChildren, y.propsChildren))
238+
|
222239
|${hookCtxsPC.result().mkString("\n")}
223240
|
224241
| // ===================================================================================================================
@@ -233,6 +250,9 @@ object GenHooks {
233250
|
234251
| class I0[+I](final val input: I)
235252
|
253+
| implicit def reusabilityI0[I](implicit I: Reusability[I]): Reusability[I0[I]] =
254+
| Reusability.by(_.input)
255+
|
236256
|${hookCtxsI.result().mkString("\n")}
237257
|}
238258
|""".stripMargin

tests/src/test/scala/japgolly/scalajs/react/core/HooksTest.scala

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -754,8 +754,8 @@ object HooksTest extends TestSuite {
754754

755755
test(comp()) { t =>
756756
t.assertText("1:1")
757-
t.clickButton(); t.assertText("2:1")
758-
t.clickButton(); t.assertText("3:1")
757+
t.clickButton(); t.assertText("2:2")
758+
t.clickButton(); t.assertText("3:3")
759759
}
760760
}
761761

@@ -1192,6 +1192,39 @@ object HooksTest extends TestSuite {
11921192
}
11931193
}
11941194

1195+
private def testRenderWithReuse(): Unit = {
1196+
implicit val reusability: Reusability[PI] = Reusability.by[PI, Int](_.pi >> 1)
1197+
var renders = 0
1198+
var extState = 5
1199+
val comp = ScalaFnComponent.withHooks[PI]
1200+
.useState(20)
1201+
.useCallback(Callback(extState += 1))
1202+
.useForceUpdate
1203+
.renderWithReuse { (p, s, incES, fu) =>
1204+
renders += 1
1205+
<.div(
1206+
s"P=$p, S=${s.value}, ES=$extState, R=$renders",
1207+
<.button(^.onClick --> s.modState(_ + 1)),
1208+
<.button(^.onClick --> (incES >> fu)),
1209+
)
1210+
}
1211+
1212+
val wrapper = ScalaComponent.builder[PI].render_P(comp(_)).build
1213+
1214+
withRenderedIntoBody(wrapper(PI(3))) { (m, root) =>
1215+
val t = new Tester(root)
1216+
t.assertText("P=PI(3), S=20, ES=5, R=1")
1217+
replaceProps(wrapper, m)(PI(2)); t.assertText("P=PI(3), S=20, ES=5, R=1")
1218+
t.clickButton(1); t.assertText("P=PI(2), S=21, ES=5, R=2")
1219+
replaceProps(wrapper, m)(PI(2)); t.assertText("P=PI(2), S=21, ES=5, R=2")
1220+
replaceProps(wrapper, m)(PI(3)); t.assertText("P=PI(2), S=21, ES=5, R=2")
1221+
replaceProps(wrapper, m)(PI(4)); t.assertText("P=PI(4), S=21, ES=5, R=3")
1222+
t.clickButton(2); t.assertText("P=PI(4), S=21, ES=6, R=4")
1223+
replaceProps(wrapper, m)(PI(5)); t.assertText("P=PI(4), S=21, ES=6, R=4")
1224+
}
1225+
}
1226+
1227+
11951228
// ===================================================================================================================
11961229

11971230
override def tests = Tests {
@@ -1256,5 +1289,6 @@ object HooksTest extends TestSuite {
12561289
}
12571290
"useStateSnapshot" - testUseStateSnapshot()
12581291
"useStateSnapshotWithReuse" - testUseStateSnapshotWithReuse()
1292+
"renderWithReuse" - testRenderWithReuse()
12591293
}
12601294
}

0 commit comments

Comments
 (0)