Announcing ReactToBindingHtml.scala: adapters for React / Binding.scala / html.scala interoperability

I am happy to announce the availability of ReactToBindingHtml.scala, which includes adapters for React / Binding.scala / html.scala interoperability.

Motivation

The rendering process in React components is unpredictable, resulting in unnecessary reevaluation and, even worse, unexpected reevaluation of side effects, if any. Precise data-binding in Binding.scala is a fast and predictable alternative to React’s repeatedly re-rendering approach.

However, currently there are more third-party components in the React ecosystem than Binding.scala. It would be nice if a web developer could reuse React components while taking advantage of Binding.scala’s precise data-binding. Therefore, the following adapters are created for reusing React components in Binding.scala + html.scala web apps:

With the help of the adapters, you will be able to reuse existing React components while getting rid of React hooks or setState by managing your app’s states in Binding.scala.

Getting Started

// build.sbt
libraryDependencies ++= Seq(
  "com.yang-bo" %%% "html" % (if (scalaBinaryVersion.value == "3") "3+" else "2+"),
  "com.yang-bo" %%% "bindingreacttoreact" % "latest.release",
  "com.yang-bo" %%% "reacttobindinghtml" % "latest.release",
  "com.yang-bo" %%% "bindinghtmltoreact" % "latest.release",
)

The following example demonstrates how to use React components with html.scala’s @html literal in Scala 2

import com.thoughtworks.binding.Binding
import com.thoughtworks.binding.Binding._
import com.yang_bo.ReactToBindingHtml.Implicits._
import com.yang_bo.BindingHtmlToReact.Implicits._
import com.yang_bo.BindingReactToReact.Implicits._
import org.scalajs.dom._
import org.lrng.binding.html
import slinky.web._
import slinky.web.html._
import slinky.core.facade._

@html def spinner(currentNumber: Var[Int]): ReactElement = {
  // virtual DOM span element
  span(
    
    // real DOM button element
    <button
      id="minus"
      onclick={ (event: MouseEvent) => currentNumber.value -= 1 }
    ></button>,
    
    // virtual DOM label element with Binding.scala's `.bind` magic
    Binding {
      label(currentNumber.bind.toString)
    },
    
    // virtual DOM button element
    button(
      id := "plus",
      onClick := { (event: Any) =>
        currentNumber.value += 1
      }
    )(
      "+"
    )

  )
}

val currentNumber = Var(50)

@html val rootView = {
  // real DOM fieldset element
  <fieldset>
    <legend>
      I am an `@html` literal that contains a React component
    </legend>
    { spinner(currentNumber) }
  </fieldset> 
}

html.render(documet.body, rootView)

For Scala 3 users, use html.scala’s html"..." interpolation instead, as shown below:

import com.thoughtworks.binding.Binding
import com.thoughtworks.binding.Binding._
import com.yang_bo.ReactToBindingHtml.Implicits._
import com.yang_bo.BindingHtmlToReact.Implicits._
import com.yang_bo.BindingReactToReact.Implicits._
import com.yang_bo.html._
import org.scalajs.dom._
import slinky.web._
import slinky.web.html._
import slinky.core.facade._

def spinner(currentNumber: Var[Int]): ReactElement = {
  // virtual DOM span element
  span(
    
    // real DOM button element
    html"""<button
      id="minus"
      onclick=${ (event: MouseEvent) => currentNumber.value -= 1 }
    ></button>""",
    
    // virtual DOM label element with Binding.scala's `.bind` magic
    Binding {
      label(currentNumber.bind.toString)
    },
    
    // virtual DOM button element
    button(
      id := "plus",
      onClick := { (event: Any) =>
        currentNumber.value += 1
      }
    )(
      "+"
    )

  )
}

val currentNumber = Var(50)

val rootView = {
  // real DOM fieldset element
  html"""<fieldset>
    <legend>
      I am an html interpolation that contains a React component
    </legend>
    ${ spinner(currentNumber) }
  </fieldset>"""
}

render(documet.body, rootView)

Note that BindingReactToReact users are recommended to neither define any React components nor use any React hooks. Instead, the application states can be managed by Binding.scala, and the BindingReactToReact React components are instantiated implicitly, when using existing React components. Because React’s virtual DOM does not support partial update provided by Binding.scala’s BindingSeq, create your own HTML UI as @html literals or html"..." interpolations, if the overhead due to React’s virtual DOM differentiation matters.

Related tools

These adapters work with React types defined in Slinky by default. Other sources of React components need to be converted to Slinky types first.

  • React components defined in scalajs-react can be converted into Slinky types via toSlinky, in order to use them in Binding.scala apps with the help of adapters from this repository.
  • React components defined in TypeScript can be converted to Slinky types via ScalablyTyped with Flavour.Slinky, in order to use them in Binding.scala apps with the help of adapters from this repository.

Links