slinky icon indicating copy to clipboard operation
slinky copied to clipboard

Typed external component

Open joan38 opened this issue 4 years ago • 7 comments

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

joan38 avatar Feb 21 '20 20:02 joan38

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 ;)

evbo avatar Jun 14 '21 00:06 evbo

That a workaround :) Thanks for sharing!

joan38 avatar Jun 16 '21 17:06 joan38

@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)

evbo avatar Jun 23 '21 01:06 evbo

I did not try. But I don't think I'll try anytime soon since I have other higher priority stuff on the fire.

joan38 avatar Jun 23 '21 02:06 joan38

@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]

shadaj avatar Jun 23 '21 02:06 shadaj

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])

evbo avatar May 24 '22 16:05 evbo

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!

evbo avatar Jan 11 '23 20:01 evbo