Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support displayName in Scala functional components #1092

Merged
merged 6 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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*`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something about the default value?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea 👍


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
Loading