Lifts Facebook's React library into Scala.js and endeavours to make it as type-safe and Scala-friendly as possible.
In addition to wrapping React, this provides extra opt-in functionality to support (separately) easier testing, and pure FP.
Additional features not available in React JS itself, are available in the extra
module.
- Setup
- Examples
- Differences from React proper
- MOAR FP! / Scalaz
- Testing
- Extensions
- Gotchas
- Alternatives
- Extra Features NEW!
- TYPES.md - Overview of types.
- Release notes and changelogs.
- React 0.12
- Scala 2.11
- Scala.JS 0.6.0+
Firstly, you'll need to add Scala.js to your project.
Next, add scalajs-react to SBT:
// Minimal usage
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "core" % "0.8.3"
// React itself
// (react-with-addons.js can be react.js, react.min.js, react-with-addons.min.js)
jsDependencies += "org.webjars" % "react" % "0.13.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.8.3" % "test"
// Scalaz support
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "ext-scalaz71" % "0.8.3"
// Monocle support
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "ext-monocle" % "0.8.3"
// Extra features (includes Scalaz and Monocle support)
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "extra" % "0.8.3"
Code:
// The basics
import japgolly.scalajs.react._
// Virtual DOM building
// There are two flavours. In both examples we will build:
// <a class="google" href="https://www.google.com"><span>GOOGLE!</span></a>
// Method 1 (recommended): Using prefixes < for tags, ^ for attributes.
import japgolly.scalajs.react.vdom.prefix_<^._
val vdom = <.a(
^.className := "google",
^.href := "https://www.google.com",
<.span("GOOGLE!"))
// Method 2: Importing everything without prefix into namespace.
import japgolly.scalajs.react.vdom.all._
val vdom = a(
className := "google",
href := "https://www.google.com",
span("GOOGLE!"))
// Scalaz support
import japgolly.scalajs.react.ScalazReact._
// Monocle support
import japgolly.scalajs.react.MonocleReact._
A number of examples are demonstrated online here.
You'll find that nearly all of the demos in the React doc are on display beside their Scala counterparts. If you know Scala and React, you should be up and running in no time.
The source code for the above lives here. To build and play around with locally:
- Checkout or download this repository.
sbt gh-pages/fastOptJS
- Open
gh-pages/index.html
locally.
- Rather than using JSX or
React.DOM.xxx
to build a virtual DOM, a specialised copy of Scalatags is used. (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. - 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.set(key = ...)
before providing the props and children.
val Example = ReactComponentB[String]("Eg").render(i => h1(i)).build
Example.set(key = "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]]
. ReactS
monads are applied via runState
. Vanilla StateT
monads (ie. without callbacks) can be lifted into ReactS
via .liftR
. 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.
A module with a extensions for Monocle also exists under ext-monocle
.
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
// JavaScript
React.addons.TestUtils.Simulate.change(t, {target: {value: "Hi"}})
becomes
// Scala
ReactTestUtils.Simulate.change(t, ChangeEventData(value = "Hi"))
// Or shorter
ChangeEventData("Hi") simulate t
Simulations can also be created and composed without a target, using Simulation
. Example:
val a = Simulation.focus
val b = Simulation.change(ChangeEventData(value = "hi"))
val c = Simulation.blur
val s = a andThen b andThen c
// Or shorter
val s = Simulation.focus >> ChangeEventData("hi").simulation >> Simulation.blur
// Or even shorter again, using a convenience method
val s = Simulation.focusChangeBlur("hi")
// Then run it later
s run component
DOM lookup is much easier than using ReactTestUtils
directly by instead using Sel
.
Sel
allows you to use a jQuery/CSS-like selector to lookup a DOM element or subset.
Full examples can be seen here; this is a sample:
val dom = Sel(".inner a.active.new") findIn myComponent
Also included is DebugJs, a dumping ground for functionality useful when testing JS. inspectObject
can be 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.12.1" % "test"
/ "react-with-addons.js" commonJSName "React"
requiresDOM := true
test in Test := (test in(Test, fastOptStage)).value
testOnly in Test := (testOnly in(Test, fastOptStage)).evaluated
testQuick in Test := (testQuick in(Test, fastOptStage)).evaluated
- Install PhantomJS.
- Case of attributes and styles matches React. So unlike vanilla-Scalatags'
onclick
attribute, useonClick
. 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"))(...)
- Attributes, styles, and tags can be wrapped in
Option
orjs.UndefOr
to make them optional.
val person: js.UndefOr[Person] = ???
val name: Option[String] = ???
val html = div(key := person.map(_.id), value := name)
EmptyTag
- A virtual DOM building block representing nothing.
div(if (allowEdit) editButton else EmptyTag)
- Custom tags, attributes and styles.
val a = "customAttr" .reactAttr
val s = "customStyle".reactStyle
val t = "customTag" .reactTag
// <customTag customAttr="hello" style="customStyle:123;">bye</customTag>
t(a := "hello", s := "123", "bye")
- 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.- 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(classSet1("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))("+")
Rather than specify references using strings, the Ref
object can provide some more safety.
Ref(name)
will create a reference to both apply to and retrieve a plain DOM node.Ref.to(component, name)
will create a reference to a component so that on retrieval its types are preserved.Ref.param(param => name)
can be used for references to items in a set, with the key being a data entity's ID.- 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())
}
Additional features are available in the extra
module.
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.