Skip to content

A guilty affair between Scala.js and Facebook's React.

License

Notifications You must be signed in to change notification settings

benjaminjackman/scalajs-react

 
 

Repository files navigation

scalajs-react Build Status

Lifts Facebook's React library into Scala.js and endeavours to make it as type-safe and Scala-compatible as possible.

Contents

Setup

SBT

// Minimal usage
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "core" % "0.4.0"

// React itself
//   (react-with-addons.js can be react.js, react.min.js, react-with-addons.min.js)
jsDependencies += "org.webjars" % "react" % "0.11.1" / "react-with-addons.js" commonJSName "React"

// Test support including ReactTestUtils
//   (requires react-with-addons.js instead of just react.js)
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "test" % "0.4.0" % "test"

// Scalaz support
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "ext-scalaz70" % "0.4.0" // or
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "ext-scalaz71" % "0.4.0"

Code:

// Typical usage
import japgolly.scalajs.react._ // React
import vdom.ReactVDom._         // Scalatags → React virtual DOM
import vdom.ReactVDom.all._     // Scalatags html & css (div, h1, textarea, etc.)

// Scalaz support
import japgolly.scalajs.react.ScalazReact._

Examples

Examples are included with this project. If you know Scala and React then that should have you up and running in no time.

If you'd like to see side-by-side comparisons of sample code taken from http://facebook.github.io/react/, do this:

  1. Checkout or download this repository.
  2. sbt fastOptJS
  3. Open example/side_by_side.html locally.

Differences from React proper

  • Rather than using JSX or React.DOM.xxx to build a virtual DOM, use ReactVDom which is backed by lihaoyi's excellent Scalatags library. (See examples.)
  • In addition to props and state, if you look at the React samples you'll see that most components need additional functions and in the case of sample #2, state outside of the designated state object (!). In this Scala version, all of that is heaped into an abstract type called Backend which you can supply or omit as necessary.
  • If you want to pass some plain text into a React component (as one of its children), then you need to wrap it in raw(). (It's a Scalatags thing.)
  • To keep a collection together when generating the dom, call .toJsArray. The only difference I'm aware of is that if the collection is maintained, React will issue warnings if you haven't supplied key attributes. Example:
    table(tbody(
      tr(th("Name"), th("Description"), th("Etcetera")),
      myListOfItems.sortBy(_.name).map(renderItem).toJsArray
    ))
  • To specify a key when creating a React component, instead of merging it into the props, call .withKey() before providing the props and children.
    val Example = ReactComponentB[String]("Eg").render(i => h1(i)).create
    Example.withKey("key1")("The Prop")

MOAR FP / Scalaz

Included is a Scalaz module that facilitates a more functional and pure approach to React integration. This is achieved primarily via state and IO monads. Joyously, this approach makes obsolete the need for a "backend".

State modifications and setState callbacks are created via ReactS, which is conceptually WriterT[M, List[Callback], StateT[M, S, A]] but caters to Scala's hopeless inability to infer types. They are applied via runState or runStateS for vanilla StateT monads (ie. without callbacks). Callbacks take the form of IO[Unit] and are hooked into HTML via ~~>, e.g. button(onclick ~~> T.runState(blah), "Click Me!").

Also included are runStateF methods which use a ChangeFilter typeclass to compare before and after states at the end of a state monad application, and optionally opt-out of a call to setState on a component.

See ScalazExamples for a taste. Take a look at the ScalazReact module for the source.

Testing

React.addons.TestUtils is wrapped in Scala and available as ReactTestUtils in the test module (see Setup). Usage is unchanged from JS.

To make event simulation easier, certain event types have dedicated, strongly-typed case classes to wrap event data. For example, JS like

React.addons.TestUtils.Simulate.change(t, {target: {value: "Hi"}})

becomes

ReactTestUtils.Simulate.change(t, ChangeEventData(value = "Hi"))

// Or alternatively,
ChangeEventData("Hi") simulate t

Also included is DebugJs, a dumping ground for functionality useful when testing JS. inspectObject is tremendously useful.

SBT

In order to test React and use ReactTestUtils you will need to make a few changes to SBT.

  • Add
jsDependencies += "org.webjars" % "react" % "0.11.1" % "test" / "react-with-addons.js" commonJSName "React"
  • Add: requiresDOM := true
  • Install PhantomJS.
  • Use jsEnv in Test to override ScalaJS's PhantomJS bind polyfill; copy from this project. (This is being tracked in ScalaJS #898)

Extensions

Scalatags

  • attr ==> (SyntheticEvent[_] => _) - Wires up an event handler.
    def handleSubmit(e: SyntheticEvent[HTMLInputElement]) = ...
    val html = form(onsubmit ==> handleSubmit)(...)
  • attr --> (=> Unit) - Specify a function as an attribute value.
    def reset() = T.setState("")
    val html = div(onclick --> reset())("Click to Reset")
  • boolean && (attr := value) - Make a condition optional.
    def hasFocus: Boolean = ...
    val html = div(hasFocus && (cls := "focus"))(...)

React

  • Where this.setState(State) is applicable, you can also run modState(State => State).
  • SyntheticEvents have aliases that don't require you to provide the dom type. So instead of SyntheticKeyboardEvent[xxx] type alias ReactKeyboardEvent can be used.
  • Because refs are not guaranteed to exist, the return type is wrapped in js.UndefOr[_]. A helper method tryFocus() has been added to focus the ref if one is returned.
    val myRef = Ref[HTMLInputElement]("refKey")

    class Backend(T: BackendScope[_, _]) {
      def clearAndFocusInput() = T.setState("", () => myRef(t).tryFocus())
    }
  • The component builder has a propsDefault method which takes some default properties and exposes constructor methods that 1) don't require any property specification, and 2) take an Optional[Props].
  • The component builder has a propsAlways method which provides all component instances with given properties, doesn't allow property specification in the constructor.
  • React has a classSet addon for specifying multiple optional class attributes. The same mechanism is applicable with this library is as follows:
    div(classSet(
      "message"           -> true,
      "message-active"    -> true,
      "message-important" -> props.isImportant,
      "message-read"      -> props.isRead
    ))(props.message)

    // Or for convenience, put all constants in the first arg:
    div(classSet("message message-active"
      ,"message-important" -> props.isImportant
      ,"message-read"      -> props.isRead
    ))(props.message)
  • Sometimes you want to allow a function to both get and affect a portion of a component's state. Anywhere that you can call .setState() you can also call focusState() to return an object that has the same .setState(), .modState() methods but only operates on a subset of the total state.
    def incrementCounter(s: ComponentStateFocus[Int]) = s.modState(_ + 1)

    // Then later in a render() method
    val f = T.focusState(_.counter)((a,b) => a.copy(counter = b))
    button(onclick --> incrementCounter(f))("+")

Gotchas

  • table(tr(...)) will appear to work fine at first then crash later. React needs table(tbody(tr(...))).
  • React doesn't apply invocations of this.setState until the end of render or the current callback. Calling .state after .setState will return the original value, ie. val s1 = x.state; x.setState(s2); x.state == s1 // not s2. If you want to compose state modifications (and you're using Scalaz), take a look at the ScalazReact module, specifically ReactS and runState.

Alternatives

Major differences:

  • Object-oriented approach.
  • Uses XML-literals instead of Scalatags. Resembles JSX very closely.

About

A guilty affair between Scala.js and Facebook's React.

Resources

License

Stars

Watchers

Forks

Packages

No packages published