I am using scalajs 0.6.15 with scalajs-react 0.11.3 and reactjs 15.4.2.
Consider a component SomeComp
where the type of a value of the Props
needs to be parameterized.
Since the type needs to be known when using the component builder I wrap the component inside a class.
class SomeComp[T]() {
case class Props(t: T, onChange: T => Callback)
val component = ReactComponentB[Props]("SomeComp")...
def apply(t: T, onChange:T => Callback) = component(Props(t, onChange))
}
this works. the problem is, that the component gets remounted with every rerender of the parent, unless the user of SomeComp
creates first an instance of SomeComp
and uses this instance in the parent's render
method.
my hack solution to avoid this is:
object SomeComp {
val genericComp = SomeComp[Any]()
def apply[T](t: T, onChange: T => Callback) = genericComp(
t = t,
onChange = (a: Any) => onChange(a.asInstanceOf[T])
)
}
is there a better, less boilercode, less awful way? any suggestions?
Update After @Golly's (thx for your elaborate answer!) suggestions I find the second solution most elegant and want to post it here for completeness:
object SomeComp {
trait Props{
type T
val t: T
val onChange: T => Callback
}
// use Props#T if you need to reference T
val component = ReactComponentB[Props]("SomeComp")...
def apply[_T](_t: _T, _onChange: _T => Callback) = {
val props = new Props {
type T = _T
val t = _t
val onChange = _onChange
}
component(props)
}
}
I made a few attempts over the years at polymorphic components and have given up on perfection. If we accept minor imperfections then there are adequate, even nice solutions:
1a) Class around builder. Exactly what you had in your first example except one important difference: only create one class per type. Creating a new class for the same type results in an un-mount/re-mount as you saw (because React thinks the components are different), where as if you do something like this:
final class SomeComp[A] private[SomeComp] () {
val component = ReactComponentB[Props]("SomeComp")...
}
object SomeComp {
val Int = new SomeComp[Int]
val String = new SomeComp[String]
}
If you don't know the types upfront, that's fine too, just create the class for your type once and put it in the companion object of the callee. It's a small imperfection that you need to create a singleton per type but everything else remains simple.
1b) Similar to above except using a function instead of a class. Create once per-type (per-callee optionally) and reuse.
2) I wouldn't recommend this but you could use existential types instead of universal, specifically make the type parameter a type member.
class Props {
type T
val value: T
val onChange: T => Callback
}
Now the component is monomorphic but you'll need some boilerplate to create and use the Props class nicely.
Btw, if you're creating a (T x T → Callback) yourself, there's a built-in one that you can use instead: https://github.com/japgolly/scalajs-react/blob/master/doc/EXTRA.md#statesnapshot (doc is for 1.0.0-RC1+, used to be two classes: {Reusable,Exeternal}Var in pre-1.0 days)