kinoplan/scalajs-react-material-ui

Get 'Ref' of a ReactBridgeComponent

Closed this issue · 5 comments

Hi There,

Is it possible to get Ref of ReactBridgeComponent? Just we can use withRef with any other component?

Regards,

Syed

Hi Syed,

it seems that we have examples of this
ComposedTextField
NativeSelects
SimpleSelect

I think this can be done in several ways:

  1. You can do this by binding to the component id tag as in the examples above.
  2. You can use untypedRef, for example:
object ComposedTextField extends ScalaCssReactImplicits {
  ...
  class Backend(t: BackendScope[Props, State]) {
    var inputRef: AnyRef = null
    
    def mount = t.modState(_.setLabelWidth(inputRef.asInstanceOf[Element])) >> t.forceUpdate

     def render(props: Props, state: State): VdomElement = {
      div(
        ...
          MuiFormControl(variant = MuiFormControl.Variant.outlined)(css.formControl,
            MuiInputLabel()(htmlFor := "component-outlined", untypedRef(inputRef = _), "Name"),
            MuiOutlinedInput(labelWidth = state.labelWidth)(
              id := "component-outlined",
              value := state.name,
              onChange ==> handleChange,
            )
          ),
        ...
      )
    }
  }
}
  1. You can't use withRef directly to ReactBridgeComponent, but you can do it by wrapping it in a ScalaComponent, for example:
object ComposedTextField extends ScalaCssReactImplicits {
  case class State(
    ...
    labelWidth: Int = 0
  ) {
    ...
    def setLabelWidth(offsetWidth: Int) = copy(labelWidth = offsetWidth)
  }
  ...

  val InputLabelComponent = ScalaComponent.builder[Unit]
    .renderStatic(MuiInputLabel()(htmlFor := "component-outlined", "Name"))
    .build

  class Backend(t: BackendScope[Props, State]) {
    private val inputRef = Ref.toScalaComponent(InputLabelComponent)

    def mount = inputRef.foreachCB { ref =>
      val offsetWidth = ref.getDOMNode.asMounted().asHtml().offsetWidth.toInt

      t.modState(_.setLabelWidth(offsetWidth))
    }

     def render(props: Props, state: State): VdomElement = {
      div(
        ...
          MuiFormControl(variant = MuiFormControl.Variant.outlined)(css.formControl,
            InputLabelComponent.withRef(inputRef)(),
            MuiOutlinedInput(labelWidth = state.labelWidth)(
              id := "component-outlined",
              value := state.name,
              onChange ==> handleChange,
            )
          ),
        ...
      )
    }
  }
}

Hi Alexey,

Thanks for your reply.

How would you define a ref for a component that defines a function. After getting the ref you need to call function on the ref.

For example, in https://www.primefaces.org/primereact/showcase/#/toast, only way to communicate with the Toast is to get the ref and call show function.

Regards,

Syed

Well, you can't do it through ReactBridgeComponent right now. Improvements are required for it.

If you do this directly through scalajs-react, then in a draft version it looks something like this:

import scala.scalajs.js
import scala.scalajs.js.JSConverters._
import scala.scalajs.js.annotation.JSImport

import japgolly.scalajs.react.vdom.all._
import japgolly.scalajs.react.{BackendScope, Children, JsComponent, Ref, ScalaComponent}

object ToastExample {
  @js.native
  trait ToastMessage extends js.Object {
    var severity: String = js.native
    var summary: String = js.native
    var detail: String = js.native
    var sticky: Boolean = js.native
  }

  object ToastMessage {
    def apply(
      severity: String,
      summary: String,
      detail: String,
      sticky: Boolean
    ) = {
      val o: Map[String, Any] = Map(
        "severity" -> severity,
        "summary" -> summary,
        "detail" -> detail,
        "sticky" -> sticky
      )

      o.toJSDictionary.asInstanceOf[js.Object].asInstanceOf[ToastMessage]
    }
  }

  object Toast {
    @JSImport("primereact/components/toast/Toast", "Toast")
    @js.native
    object RawComponent extends js.Function

    @js.native
    trait Props extends js.Object {
      var position: String = js.native
    }

    @js.native
    trait JsMethods extends js.Object {
      def show(message: js.Array[ToastMessage]): Unit = js.native
      def show(message: ToastMessage): Unit = js.native

      def clear(): Unit = js.native
    }

    def props(position: String): Props = {
      val p = (new js.Object).asInstanceOf[Props]
      p.position = position
      p
    }

    val component = JsComponent[Props, Children.None, Null](RawComponent).addFacade[JsMethods]
  }

  class Backend(t: BackendScope[Unit, Unit]) {
    private val toastRef = Ref.toJsComponentWithMountedFacade[Toast.Props, Null, Toast.JsMethods]

    def showSuccess() = {
      val message = ToastMessage("success", "Success Message", "Message Content", sticky = true)

      toastRef.foreach(_.raw.show(message))
    }

    def render(): VdomElement = {
      div(
        Toast.component.withRef(toastRef)(Toast.props(position = "top-left")),
        button(onClick --> showSuccess(), "Success")
      )
    }
  }

  private val component = ScalaComponent.builder[Unit]("ToastExample")
    .renderBackend[Backend]
    .build

  def apply() = component()
}

Hi Alexey,

Thanks for reply, I would try this.

Regards,

Syed

Hi Alexey,

Thanks for your reply, I tried the code snippet as you mentioned and it worked.

I am closing this issue.

Regards,

Syed