slinky
slinky copied to clipboard
Typed external component
Given the following working code:
import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import scala.scalajs.js.{|, undefined, UndefOr}
import slinky.core.{ExternalComponentWithAttributes, TagMod}
import slinky.core.annotations.react
import slinky.web.html._
@react object Autocomplete extends ExternalComponentWithAttributes[*.tag.type] {
@JSImport("@material-ui/lab/Autocomplete", JSImport.Default) @js.native
private object Autocomplete extends js.Object
case class Props(
options: Seq[String],
renderInput: js.Object with js.Dynamic => TagMod[*.tag.type],
value: UndefOr[String | Seq[String]] = undefined,
onChange: (js.Object, String) => Unit = (_, _) => (),
disabled: Boolean = false,
loading: Boolean = false,
disableClearable: Boolean = false,
autoHighlight: Boolean = false,
loadingText: String = "Loading..."
)
override val component: String | js.Object = Autocomplete
}
I'd like to make have generic typed props as the non working following code:
import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import scala.scalajs.js.{|, undefined, UndefOr}
import slinky.core.{ExternalComponentWithAttributes, TagMod}
import slinky.core.annotations.react
import slinky.web.html._
@react object Autocomplete extends ExternalComponentWithAttributes[*.tag.type] {
@JSImport("@material-ui/lab/Autocomplete", JSImport.Default) @js.native
private object Autocomplete extends js.Object
case class Props[T](
options: Seq[T],
renderInput: js.Object with js.Dynamic => TagMod[*.tag.type],
value: UndefOr[T | Seq[T]] = undefined,
onChange: (js.Object, T) => Unit = (_, _) => (),
disabled: Boolean = false,
loading: Boolean = false,
disableClearable: Boolean = false,
autoHighlight: Boolean = false,
loadingText: String = "Loading..."
)
override val component: String | js.Object = Autocomplete
}
Unless I'm missing something this is not currently supported or not documented. Is there a workaround?
Thanks
this is how I create typed slinky components, could something similar work for external?:
// here's how I type my components when creating them:
div(
// this could be instantiated once in a singleton object alternatively...
new NeedTyping[SubMyType]().MyComponent(thing = someTypedThing)
)
// where the component is defined like:
// or can be case class too
class NeedTyping[T <: MyType] extends AndCanDoHigherOrderToo[T] {
@react object MyComponent {
case class Props(thing: T)
val component = FunctionalComponent[Props] { props =>
// do stuff...
}
}
}
Works for me ;)
That a workaround :) Thanks for sharing!
@joan38 did it work for you? One issue I noticed is if my components have callbacks where the javascript sends generic arguments, I'd get warnings such as the following and it wouldn't work quite right at runtime:
Using fallback derivation for type T => Unit (derivation: MacroWritersImpl)
I did not try. But I don't think I'll try anytime soon since I have other higher priority stuff on the fire.
@evbo you should be able to resolve this by requiring typeclass instances at the NeedTyping
level, so your definition would become something like:
class NeedTyping[T <: MyType : Reader: Writer]
and just for the uninitiated, class NeedTyping[T <: MyType : Reader: Writer]
is called a context bound and is shorthand for adding implicit value arguments to the class: class NeedTyping[T <: MyType](implicit r: Reader[T], w: Writer[T])
One extremely painful side effect of this approach is each time you instantiate the class
it will cause React to Remount
. I had an input
defined inside a component that kept losing state every render. This turned out to be the reason why and it was very hard to catch.
So exercise extreme caution: do not instantiate inside a render! If you're using hooks, always instantiate outside your val component
definition!