Skip to content

Commit

Permalink
Merge pull request #1092 from japgolly/display-name
Browse files Browse the repository at this point in the history
Support displayName in Scala functional components
  • Loading branch information
rpiaggio authored Mar 6, 2024
2 parents 3e88f01 + e67690f commit fdb260b
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 217 deletions.
13 changes: 13 additions & 0 deletions doc/changelog/2.2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ TODO: Add demo/doc for new testing API
commonJSName "ReactDOMServer",
),
```

- `ScalaFnComponent` and `ScalaForwardRef` now support `.withDisplayName(name)`. Must be called first (before hooks/`render*`/`withChildren*`).

Example:

```scala
ScalaFnComponent.withDisplayName("MyComponent")
.withHooks[Props]
.useState(0)
.render((_, s) => s.value.toString)
```

If not specified, a unique name will be inferred from the fully qualified name of the call site.
16 changes: 8 additions & 8 deletions downstream-tests/js/src/test/scala/downstream/MimaTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ object MimaTests extends TestSuite {

override def tests = Tests {

"2_0_0" - {
import mima200._
// "2_0_0" - {
// import mima200._

"HookUseRef" - HookUseRef.test { ref =>
val a = ref.value
val b = ref.map(_ + 1).unsafeGet()
assertEq(b, a + 1)
}
}
// "HookUseRef" - HookUseRef.test { ref =>
// val a = ref.value
// val b = ref.map(_ + 1).unsafeGet()
// assertEq(b, a + 1)
// }
// }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ object JsFn extends JsBaseComponentTemplate[facade.React.StatelessFunctionalComp
generic[UnusedObject, Children.Varargs](p => render(PropsChildren(p.children)))(s)
}

private def staticDisplayName = "<FnComponent>"
private def readDisplayName(a: facade.HasDisplayName): String =
a.displayName.getOrElse("")

override protected def rawComponentDisplayName[A <: js.Object](r: facade.React.StatelessFunctionalComponent[A]) =
staticDisplayName
readDisplayName(r)

// ===================================================================================================================

Expand Down Expand Up @@ -113,7 +114,7 @@ object JsFn extends JsBaseComponentTemplate[facade.React.StatelessFunctionalComp
sealed trait UnmountedSimple[P, M] extends Generic.UnmountedSimple[P, M] {
override type Raw <: facade.React.ComponentElement[_ <: js.Object]
override final type Ref = Nothing
override final def displayName = staticDisplayName
override final def displayName = readDisplayName(raw.`type`)

override def mapUnmountedProps[P2](f: P => P2): UnmountedSimple[P2, M]
override def mapMounted[M2](f: M => M2): UnmountedSimple[P, M2]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import japgolly.scalajs.react.internal._
import japgolly.scalajs.react.vdom.VdomNode
import japgolly.scalajs.react.{Children, CtorType, PropsChildren, Reusability, facade}
import scala.scalajs.js
import sourcecode.FullName

object ScalaFn {

Expand All @@ -13,41 +14,79 @@ object ScalaFn {
type Mounted = JsFn.Mounted

private def create[P, C <: Children, CT[-p, +u] <: CtorType[p, u]]
(displayName: String)
(render: Box[P] with facade.PropsWithChildren => VdomNode)
(implicit s: CtorType.Summoner.Aux[Box[P], C, CT]): Component[P, CT] = {

val jsRender = render.andThen(_.rawNode): js.Function1[Box[P] with facade.PropsWithChildren, facade.React.Node]
val rawComponent = jsRender.asInstanceOf[facade.React.StatelessFunctionalComponent[Box[P]]]
rawComponent.setDisplayName = displayName
JsFn.force[Box[P], C](rawComponent)(s)
.cmapCtorProps[P](Box(_))
.mapUnmounted(_.mapUnmountedProps(_.unbox))
}

@inline def withHooks[P] =
HookComponentBuilder.apply[P]
private def derivedDisplayName(implicit name: FullName): String =
name.value

@inline def withDisplayName(name: String): DisplayNameApplied =
new DisplayNameApplied(name)

@inline def withHooks[P](implicit name: FullName): HookComponentBuilder.ComponentP.First[P] =
HookComponentBuilder.apply[P](derivedDisplayName)

// ===================================================================================================================

def apply[P](render: P => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None]): Component[P, s.CT] =
create[P, Children.None, s.CT](b => render(b.unbox))(s)
def apply[P](render: P => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None], name: FullName): Component[P, s.CT] =
create[P, Children.None, s.CT](derivedDisplayName)(b => render(b.unbox))(s)

def withChildren[P](render: (P, PropsChildren) => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs]): Component[P, s.CT] =
create[P, Children.Varargs, s.CT](b => render(b.unbox, PropsChildren(b.children)))(s)
def withChildren[P](render: (P, PropsChildren) => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs], name: FullName): Component[P, s.CT] =
create[P, Children.Varargs, s.CT](derivedDisplayName)(b => render(b.unbox, PropsChildren(b.children)))(s)

def justChildren(render: PropsChildren => VdomNode): Component[Unit, CtorType.Children] =
create(b => render(PropsChildren(b.children)))
def justChildren(render: PropsChildren => VdomNode)(implicit name: FullName): Component[Unit, CtorType.Children] =
create(derivedDisplayName)(b => render(PropsChildren(b.children)))

// ===================================================================================================================

def withReuse[P](render: P => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None], r: Reusability[P]): Component[P, s.CT] =
def withReuse[P](render: P => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None], r: Reusability[P], name: FullName): Component[P, s.CT] =
withHooks[P].renderWithReuse(render)(s, r)

def withReuseBy[P, A](reusableInputs: P => A)(render: A => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None], r: Reusability[A]): Component[P, s.CT] =
def withReuseBy[P, A](reusableInputs: P => A)(render: A => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None], r: Reusability[A], name: FullName): Component[P, s.CT] =
withHooks[P].renderWithReuseBy(reusableInputs)(render)(s, r)

def withChildrenAndReuse[P](render: (P, PropsChildren) => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs], rp: Reusability[P], rc: Reusability[PropsChildren]): Component[P, s.CT] =
def withChildrenAndReuse[P](render: (P, PropsChildren) => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs], rp: Reusability[P], rc: Reusability[PropsChildren], name: FullName): Component[P, s.CT] =
withHooks[P].withPropsChildren.renderWithReuse(i => render(i.props, i.propsChildren))

def withChildrenAndReuse[P, A](reusableInputs: (P, PropsChildren) => A)(render: A => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs], r: Reusability[A]): Component[P, s.CT] =
def withChildrenAndReuse[P, A](reusableInputs: (P, PropsChildren) => A)(render: A => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs], r: Reusability[A], name: FullName): Component[P, s.CT] =
withHooks[P].withPropsChildren.renderWithReuseBy(i => reusableInputs(i.props, i.propsChildren))(render)

class DisplayNameApplied private[ScalaFn](displayName: String) {
@inline def withHooks[P]: HookComponentBuilder.ComponentP.First[P] =
HookComponentBuilder.apply[P](displayName)

// ===================================================================================================================

def apply[P](render: P => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None]): Component[P, s.CT] =
create[P, Children.None, s.CT](displayName)(b => render(b.unbox))(s)

def withChildren[P](render: (P, PropsChildren) => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs]): Component[P, s.CT] =
create[P, Children.Varargs, s.CT](displayName)(b => render(b.unbox, PropsChildren(b.children)))(s)

def justChildren(render: PropsChildren => VdomNode): Component[Unit, CtorType.Children] =
create(displayName)(b => render(PropsChildren(b.children)))

// ===================================================================================================================

def withReuse[P](render: P => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None], r: Reusability[P]): Component[P, s.CT] =
withHooks[P].renderWithReuse(render)(s, r)

def withReuseBy[P, A](reusableInputs: P => A)(render: A => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.None], r: Reusability[A]): Component[P, s.CT] =
withHooks[P].renderWithReuseBy(reusableInputs)(render)(s, r)

def withChildrenAndReuse[P](render: (P, PropsChildren) => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs], rp: Reusability[P], rc: Reusability[PropsChildren]): Component[P, s.CT] =
withHooks[P].withPropsChildren.renderWithReuse(i => render(i.props, i.propsChildren))

def withChildrenAndReuse[P, A](reusableInputs: (P, PropsChildren) => A)(render: A => VdomNode)(implicit s: CtorType.Summoner[Box[P], Children.Varargs], r: Reusability[A]): Component[P, s.CT] =
withHooks[P].withPropsChildren.renderWithReuseBy(i => reusableInputs(i.props, i.propsChildren))(render)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import japgolly.scalajs.react.vdom.VdomNode
import japgolly.scalajs.react.{Children, CtorType, PropsChildren, Ref, facade}
import scala.annotation.nowarn
import scala.scalajs.js
import sourcecode.FullName


object ScalaForwardRef {
type Component[P, R, CT[-p, +u] <: CtorType[p, u]] = JsForwardRef.ComponentWithRoot[P, R, CT, Unmounted[P, R], Box[P], CT, JsForwardRef.Unmounted[Box[P], R]]
Expand All @@ -20,20 +22,24 @@ object ReactForwardRefInternals {
protected type RefValue

protected def create[P, C <: Children, CT[-p, +u] <: CtorType[p, u]]
(displayName: String)
(render: (Box[P] with facade.PropsWithChildren, Option[R]) => VdomNode)
(implicit s: CtorType.Summoner.Aux[Box[P], C, CT]): Component[P, RefValue, CT]

final def apply(render: Option[R] => VdomNode): Component[Unit, RefValue, CtorType.Nullary] =
create((_, r) => render(r))
private def derivedDisplayName(implicit name: FullName): String =
name.value

final def apply(render: Option[R] => VdomNode)(implicit name: FullName): Component[Unit, RefValue, CtorType.Nullary] =
create(derivedDisplayName)((_, r) => render(r))

final def apply[P](render: (P, Option[R]) => VdomNode): Component[P, RefValue, CtorType.Props] =
create((p, r) => render(p.unbox, r))
final def apply[P](render: (P, Option[R]) => VdomNode)(implicit name: FullName): Component[P, RefValue, CtorType.Props] =
create(derivedDisplayName)((p, r) => render(p.unbox, r))

final def withChildren[P](render: (P, PropsChildren, Option[R]) => VdomNode): Component[P, RefValue, CtorType.PropsAndChildren] =
create((b, r) => render(b.unbox, PropsChildren(b.children), r))
final def withChildren[P](render: (P, PropsChildren, Option[R]) => VdomNode)(implicit name: FullName): Component[P, RefValue, CtorType.PropsAndChildren] =
create(derivedDisplayName)((b, r) => render(b.unbox, PropsChildren(b.children), r))

final def justChildren(render: (PropsChildren, Option[R]) => VdomNode): Component[Unit, RefValue, CtorType.Children] =
create((b, r) => render(PropsChildren(b.children), r))
final def justChildren(render: (PropsChildren, Option[R]) => VdomNode)(implicit name: FullName): Component[Unit, RefValue, CtorType.Children] =
create(derivedDisplayName)((b, r) => render(PropsChildren(b.children), r))
}

// extends AnyVal with Dsl makes scalac 2.11 explode
Expand All @@ -42,11 +48,29 @@ object ReactForwardRefInternals {
override protected type RefValue = RM

override protected def create[P, C <: Children, CT[-p, +u] <: CtorType[p, u]]
(displayName: String)
(render: (Box[P] with facade.PropsWithChildren, Option[R]) => VdomNode)
(implicit s: CtorType.Summoner.Aux[Box[P], C, CT]): Component[P, RefValue, CT] =
ReactForwardRef.create[P, RefValue, C, CT]((p, r) => render(p, r.map(_.map(
ReactForwardRef.create[P, RefValue, C, CT](displayName)((p, r) => render(p, r.map(_.map(
Js.mounted[P0, S0](_).withRawType[RM]
))))

@inline def withDisplayName(name: String): DisplayNameApplied =
new DisplayNameApplied(name)

class DisplayNameApplied private[ToJsComponent](displayName: String) {
final def apply(render: Option[R] => VdomNode): Component[Unit, RefValue, CtorType.Nullary] =
create(displayName)((_, r) => render(r))

final def apply[P](render: (P, Option[R]) => VdomNode): Component[P, RefValue, CtorType.Props] =
create(displayName)((p, r) => render(p.unbox, r))

final def withChildren[P](render: (P, PropsChildren, Option[R]) => VdomNode): Component[P, RefValue, CtorType.PropsAndChildren] =
create(displayName)((b, r) => render(b.unbox, PropsChildren(b.children), r))

final def justChildren(render: (PropsChildren, Option[R]) => VdomNode): Component[Unit, RefValue, CtorType.Children] =
create(displayName)((b, r) => render(PropsChildren(b.children), r))
}
}

// extends AnyVal with Dsl makes scalac 2.11 explode
Expand All @@ -55,16 +79,35 @@ object ReactForwardRefInternals {
override protected type RefValue = Scala.RawMounted[P2, S, B]

override protected def create[P, C <: Children, CT[-p, +u] <: CtorType[p, u]]
(displayName: String)
(render: (Box[P] with facade.PropsWithChildren, Option[R]) => VdomNode)
(implicit s: CtorType.Summoner.Aux[Box[P], C, CT]): Component[P, RefValue, CT] =
ReactForwardRef.create[P, RefValue, C, CT]((p, r) => render(p, r.map(_.map(_.mountedImpure))))
ReactForwardRef.create[P, RefValue, C, CT](displayName)((p, r) => render(p, r.map(_.map(_.mountedImpure))))

@inline def withDisplayName(name: String): DisplayNameApplied =
new DisplayNameApplied(name)

class DisplayNameApplied private[ToScalaComponent](displayName: String) {
final def apply(render: Option[R] => VdomNode): Component[Unit, RefValue, CtorType.Nullary] =
create(displayName)((_, r) => render(r))

final def apply[P](render: (P, Option[R]) => VdomNode): Component[P, RefValue, CtorType.Props] =
create(displayName)((p, r) => render(p.unbox, r))

final def withChildren[P](render: (P, PropsChildren, Option[R]) => VdomNode): Component[P, RefValue, CtorType.PropsAndChildren] =
create(displayName)((b, r) => render(b.unbox, PropsChildren(b.children), r))

final def justChildren(render: (PropsChildren, Option[R]) => VdomNode): Component[Unit, RefValue, CtorType.Children] =
create(displayName)((b, r) => render(PropsChildren(b.children), r))
}
}
}

object ReactForwardRef { outer =>
import ReactForwardRefInternals._

private[component] def create[P, R, C <: Children, CT[-p, +u] <: CtorType[p, u]]
(displayName: String)
(render: (Box[P] with facade.PropsWithChildren, Option[Ref.Simple[R]]) => VdomNode)
(implicit s: CtorType.Summoner.Aux[Box[P], C, CT]): Component[P, R, CT] = {

Expand All @@ -73,23 +116,31 @@ object ReactForwardRef { outer =>
render(p, Ref.forwardedFromJs(r)).rawNode

val rawComponent = facade.React.forwardRef(jsRender)
rawComponent.displayName = displayName

JsForwardRef.force[Box[P], C, R](rawComponent)(s)
.cmapCtorProps[P](Box(_))
.mapUnmounted(_.mapUnmountedProps(_.unbox))
}

def apply[R](render: Option[Ref.Simple[R]] => VdomNode): Component[Unit, R, CtorType.Nullary] =
create((_, r) => render(r))
private def derivedDisplayName(implicit name: FullName): String =
name.value

@inline def withDisplayName(name: String): DisplayNameApplied =
new DisplayNameApplied(name)


def apply[R](render: Option[Ref.Simple[R]] => VdomNode)(implicit name: FullName): Component[Unit, R, CtorType.Nullary] =
create(derivedDisplayName)((_, r) => render(r))

def apply[P, R](render: (P, Option[Ref.Simple[R]]) => VdomNode): Component[P, R, CtorType.Props] =
create((p, r) => render(p.unbox, r))
def apply[P, R](render: (P, Option[Ref.Simple[R]]) => VdomNode)(implicit name: FullName): Component[P, R, CtorType.Props] =
create(derivedDisplayName)((p, r) => render(p.unbox, r))

def withChildren[P, R](render: (P, PropsChildren, Option[Ref.Simple[R]]) => VdomNode): Component[P, R, CtorType.PropsAndChildren] =
create((b, r) => render(b.unbox, PropsChildren(b.children), r))
def withChildren[P, R](render: (P, PropsChildren, Option[Ref.Simple[R]]) => VdomNode)(implicit name: FullName): Component[P, R, CtorType.PropsAndChildren] =
create(derivedDisplayName)((b, r) => render(b.unbox, PropsChildren(b.children), r))

def justChildren[R](render: (PropsChildren, Option[Ref.Simple[R]]) => VdomNode): Component[Unit, R, CtorType.Children] =
create((b, r) => render(PropsChildren(b.children), r))
def justChildren[R](render: (PropsChildren, Option[Ref.Simple[R]]) => VdomNode)(implicit name: FullName): Component[Unit, R, CtorType.Children] =
create(derivedDisplayName)((b, r) => render(PropsChildren(b.children), r))

// ===================================================================================================================

Expand All @@ -104,4 +155,18 @@ object ReactForwardRef { outer =>

def toScalaComponent[P, S, B]: ToScalaComponent[P, S, B] =
new ToScalaComponent(())

class DisplayNameApplied private[ReactForwardRef](displayName: String) {
def apply[R](render: Option[Ref.Simple[R]] => VdomNode): Component[Unit, R, CtorType.Nullary] =
create(displayName)((_, r) => render(r))

def apply[P, R](render: (P, Option[Ref.Simple[R]]) => VdomNode): Component[P, R, CtorType.Props] =
create(displayName)((p, r) => render(p.unbox, r))

def withChildren[P, R](render: (P, PropsChildren, Option[Ref.Simple[R]]) => VdomNode): Component[P, R, CtorType.PropsAndChildren] =
create(displayName)((b, r) => render(b.unbox, PropsChildren(b.children), r))

def justChildren[R](render: (PropsChildren, Option[Ref.Simple[R]]) => VdomNode): Component[Unit, R, CtorType.Children] =
create(displayName)((b, r) => render(PropsChildren(b.children), r))
}
}
Loading

0 comments on commit fdb260b

Please sign in to comment.