Lifts Facebook's React library into Scala.js and endeavours to make it as type-safe and Scala-compatible as possible.
- Setup
- Examples
- Differences from React proper
- MOAR FP! / Scalaz
- Testing
- Extensions
- Gotchas
- Alternatives
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 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:
- Checkout or download this repository.
sbt fastOptJS
- Open
example/side_by_side.html
locally.
- Rather than using JSX or
React.DOM.xxx
to build a virtual DOM, useReactVDom
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 suppliedkey
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")
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.
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.
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 PhantomJSbind
polyfill; copy from this project. (This is being tracked in ScalaJS #898)
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"))(...)
- Extra attributes not yet found in Scalatags proper.
- Where
this.setState(State)
is applicable, you can also runmodState(State => State)
. SyntheticEvent
s have aliases that don't require you to provide the dom type. So instead ofSyntheticKeyboardEvent[xxx]
type aliasReactKeyboardEvent
can be used.- Because refs are not guaranteed to exist, the return type is wrapped in
js.UndefOr[_]
. A helper methodtryFocus()
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 anOptional[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 callfocusState()
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))("+")
table(tr(...))
will appear to work fine at first then crash later. React needstable(tbody(tr(...)))
.- React doesn't apply invocations of
this.setState
until the end ofrender
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 theScalazReact
module, specificallyReactS
andrunState
.
Major differences:
- Object-oriented approach.
- Uses XML-literals instead of Scalatags. Resembles JSX very closely.