This describes the changes between v0.11.3 and v1.0.0. Notes for the 1.0 release canditates are here.
This is one big document. Everything's changed. Grab a coffee.
scalajs-react
was originally a proof-of-concept, experimenting both with React and Scala.JS back in 2014.
Once the concept was validated it immediately began being used in anger in a real-world project,
and over the years this drove the discovery of additional required changes and features.
These changes were usually implemented via hacks due a lack of initial design and discovered direction.
Oft times how to solve certain problems was unclear and begat more experimentation which created more churn and mess.
To be honest, I was also less experienced in Scala than I am today, and wrote code in a way
that sometimes defied readability and easy comprehension when in a team:
implicits everywhere, symbols and abbreviations all over the place (Haskell influence), etc.
In the end, the v0.x branch ended up being a codebase too design-constrained and near impossible to extend, hacks everywhere, littered with experiments both successful and failed.
It was time to give this library a proper, solid foundation. This time around, there was plenty of 20/20 hindsight and real-world project code to draw on to guide tough decisions. It still doesn't have everything imaginable, but it is infinitely more extensible now, meaning it will be much easier where difficult, and possible where impossible, to add more functionality to this. It's even possible for people to start making external modules for their features if they want to, as the library is finally open enough to support it.
I think it's ended up in a really good place. I hope you enjoy the new experience.
The vast majority of the core
module has been rewritten from scratch.
The other modules have received major revision too and in some cases, rewriting.
Both the library internals and the code you write as a library user are much simpler now.
Complex, verbose types have been simplified (no more N <: TopNode
everywhere).
Changes are simple to make and don't require hacks anymore.
Comprehending and maintaining existing functionality is easy now.
An effort has also been made to make the experience consistent. Different parts of the library that do related things, now have consistent names and a consistent API. This is more cohesive, less surprising and ultimately less to remember.
Contributing to Simplification, is Reduction. Similar concepts, structures, and utilities have been consolidated. Experiments that were too noisy and not useful enough have been removed. So too have little utilities that didn't provide enough benefit.
There's now less to learn, less to remember, less caveats to consider, and a consistent way of doing things.
A common, very valid complaint was that it had poor readability.
There were too many symbols, inconsistencies in the API,
single-letter suffixes all over the place, non-obvious abbreviations.
(Example: Px.bs($).propsM
)
All (ok, 98%) of this has been removed and replaced with a clearer API. In some places it's more verbose now but really, especially with auto-complete, what's an extra 10 chars on a line, every now and then? The readability improvement is worth it. Less cognitive load.
As everything previously grew from JS facades, there was a lot of implicits and monkey-patching.
No longer. Nothing in components is hidden anymore, types provide full access to their lineage
and you can see exactly how the layers stack.
For example it's completely transparent that (and how) a ScalaComponent
builds on a JsComponent
which in turn has a raw JS-world value.
It is now easy-peasy for scalajs-react code to
- use React components written in other languages
- be used from other languages
- interoperate with other Scala.JS React libraries
There are two main reasons for this.
- There is now an underlying, generic representation of components. Language-agnostic. Scala components (created with
ScalaComponent.builder
, previouslyReactComponentB
) are no longer a special case and have no special privileges in the library. One can now choose to ignore the builder completely and use another mechanism to create their components. - The Transparency changes noted above. As it's trivial to both get at raw (underlying) JS values and see the transformations on top of them now, it will be straightforward to use raw JS values as the bridge and just extract or wrap as necessary.
-
Generic representations. There is now a generic interface for components and their unmounted & mounted states. This allows any kind of component (JS, Clojure, ES6 classes, some custom super-simple Scala wizard) to be used and integrate with the library. Many library features now work with and provide functionality to all kinds of components. Want Scalaz extensions to some Typescript-based component you found and are using in Scala? done! Want access to a parent component's state regardless of what kind of component it is, regardless of whether the state you're interested in is a subset or whole, has transformation or not? done!
-
Proper representation of non-Scala components. Various types of components, and their idiosyncrasies, can now be modelled easily. Included in the library are four types:
-
JsComponent
- It's now a one-liner (not counting Scala.JS facades) to use community components written in pure JS. No more hacks!@JSName("Blah") // If you're not using modules @JSImport("./blah.js", "Blah") // If you're using modules @js.native object BlahJs extends js.Object val Blah = JsComponent[JsProps, Children.None, JsState](BlahJs)
js.Dynamic
also works:val Blah = JsComponent[JsProps, Children.None, JsState](js.Dynamic.global.Blah)
You can even get type-safety for JS components that expose ad-hoc methods once mounted; just specify the JS facade.
val Blah = JsComponent[JsProps, Children.None, JsState](BlahJs).addFacade[BlahFacade]
-
JsFnComponent
- React "functional components" written in pure JS. Also a one-liner to create, similar to above. -
ScalaComponent
- Create React components in Scala using the functional builder with an optional backend. This is the best way to write components and is still around. Where as there used to beReactComponentB[Props]…
before, it's nowScalaComponent.builder[Props]…
.
There's alsoScalaComponent.static
for creating efficient, static views. -
ScalaFnComponent
- Create React "functional components" in Scala. Basically aJsFnComponent
built on a Scala function, compatible with Scala types.
-
-
Transformation of ALL component type parameters. When you have a component (or its Unmounted or Mounted stages), you can now completely map everything that goes in and out of it. Specifically you can:
- Transform props types and values (at top-level, and in Unmounted & Mounted too).
- Transform state types and values (at top-level, and in Unmounted & Mounted too).
- Transform the constructor.
- Transform the Unmounted representation.
- Transform the Mounted representation.
- Change the effect type once Mounted.
-
Transparency. No more hidden magic.
- All JS facades are in a separate
.raw
package (without any Scala niceness). - All components expose their underlying JS values. Call
.raw
on anything to get what you'd have if you were using pure JS. - JS-based components have a Scala representation that allows nice, safe usage. It's transparent that you've got a Scala facade over the raw JS.
- The structure of components that you create using the Scala Builder are completely transparent too.
You have access to the underlying
JsComponent
(showing exactly how the Scala boxing and backend support is implemented), and access to its raw JS value. - Nearly no more implicits are used. Nearly all the methods are now directly on the objects.
- All JS facades are in a separate
-
Interoperability. It should be trivial to mix
scalajs-react
with other React libraries, regardless of the language.- Using
scalajs-react
from JS or another language: due to the transparency changes you have full access to all underlying JS values and any transformations required. - Using
scalajs-react
from another Scala-based React library: see above point, one could create implicit conversions as necessary. - Using JS from
scalajs-react
: one-liner; see above. - Using another language or Scala-based library from
scalajs-react
: either grab the pure JS values and call a few methods to add any required transformations, or create your own representations by extending the generic traits in the library.
- Using
-
Proper constructors. More accuracy, safety, flexibility, extensibility.
- Children are now finally declared.
There are currently two declarations:
Children.None
andChildren.Varargs
. This means that you can no longer pass children to a component that won't use them, and you can no longer forget to pass children to a component that uses them. It might be an idea to add another case for components that want exactly one child, and enforce it in the constructor but this hasn't been done yet. The code is open to it which is nice. - Props values can be pre-specified.
This happens automatically when creating components with singleton types like
Unit
andNull
. It's also customisable in various ways. - Input and output can be transformed.
- Additional fields on the raw/underlying JS props object can be specified.
- Example usage:
UserProfileComponent(userId) // Component with props and Children.None BorderComponent("red")(h1("Hi!"), p("bye")) // Component with props and Children.Varargs RedBorderComponent(h1("Hi!"), p("bye")) // Component with preset props and Children.Varargs DateTimeComponent() // Component with preset props and Children.None
- Children are now finally declared.
There are currently two declarations:
-
Virtual DOM major revision
-
Under-the-hood, the types have been rewritten and simplified, and is now easier to work and maintain. This no longer bears any resemblance to Scalatags. Scalatags was however, tremendously helpful in this journey so if you have a chance, please join me in giving @lihaoyi a big thanks for his work.
-
VDOM attributes now have type-safety with regard to their values. Eg
button(disabled := 123)
no longer compiles because the library knows that thedisabled
attribute requires a boolean;button(disabled := true)
works. -
Event-handler attributes now know which types of events they accept. Eg
onMouseDown
knows to expect a mouse event and won't compile if you pass it a drag event handler. -
No more automatic expansion of
Seq
s. Either add.toTagMod
or flatten it out of an array, or use.toVdomArray
to turn it into an array from React's perspective (which requires keys). -
Optional VDOM supported when enclosed in
Option
,js.UndefOr
, orscalaz.Maybe
. -
All VDOM now has
.when(condition)
and.unless(condition)
, allowing you to conditionally include/omit dom. This replaces thecond ?= (vdom)
syntax.Note, that evaluation semantics have changed:
vdom
was a "by name" parameter incond ?= (vdom)
, providing lazy semantics. When using.when(condition)
and.unless(condition)
, vdom is evaluated, regardless of the condition value. -
React node array handling is safer, more efficient and has its own type (
VdomArray
) with a nicer interface. -
Manually-specified
style
objects now compose with other style attributes. -
Improved efficiency for VDOM representation and creation.
-
Smaller JS output size.
-
All VDOM is now composable via
.apply
. This was usually the case but there were a few corner cases that had differences. When it comes toTagMod
, the+
operator has been removed -- to join many at once, instead ofa + b + c + …
, useTagMod(a, b, c, …)
or the less efficienta(b)(c)(…)
. -
There's now a VDOM cheatsheet.
-
-
Refs revision
- React.JS has deprecated and will eventually remove String-based refs, and so they have been removed from scalajs-react.
- New type-safe refs to HTML/SVG tags that preserve the DOM type.
- New type-safe refs to JS components.
- New type-safe refs to Scala components.
- Functional components do not support refs and so, there is no mechanism to do so anymore. In scalajs-react v0.x you could create refs to anything so this is an improvement.
-
PropsChildren
now has its own type that provides simple, idiomatic Scala usage. -
Name revision
There are many types and methods that remain from v0.x and have been renamed for clarity and readability. Gone are the abbreviations, the symbols like
$
and_
, the one letter suffixes, etc. Not all cases, but the vast majority. The result is code can occasionally be a little bit more verbose but much clearer. And don't worry about having to tediously update your codebase; there is a migration script will take of it for you. -
Simple types for various purposes
The underlying types used by components are complex. It's necessary to support all the desirable features we now have. What's not necessary is for that to leak into users' code. There are now many simple ways of accessing and/or abstracting over components. Which one you choose depends on the use-case and there is a guide in TYPES.md.
Also: components no longer track DOM types. In cases where accessing a component's DOM is desirable, add a method to the backend (or externally) that casts it. Example:
import org.scalajs.dom.html import japgolly.scalajs.react._ object MyComponent { // ... class Backend(bs: BackendScope[Props, Unit]) { // Cast it yourself when desired def getDom: CallbackTo[html.TextArea] = bs.getDOMNode.map(_.domCast[html.TextArea]) } }
-
ES6 classes
Components created with
ScalaComponent.builder
now result in ES6 classes under-the-hood, instead usingReact.createClass
which was deprecated in React v15.5. There is also a raw facade for ES6 classes, and external React classes integrate with scalajs-react like any other components.
-
Reduction:
ExternalVar
andReusableVar
have been consolidated into a single class calledStateSnapshot
. The advantages are:- Reusability is now optional. Component clients don't have to jump through hoops if they don't care about reusability.
- No more having to change entire call graphs when adding/removing reusability (as was the case when deciding to switch
ExternalVar
forReusableVar
or vica-versa.) - Users have one type to learn, and no longer have to learn subtleties between two very similar types.
-
The
StateSnapshot
(previouslyExternalVar
andReusableVar
) object now has a nice construction mini-DSL and even supports zooming. -
Reduction:
ReusableFn
,ReusabaleVal
, andReusabaleVal2
have all been consolidated into a single class calledReusable
. This means we now simply haveReusability[A]
andReusable[A]
, the difference being thatReusability[A]
is for ∀a∈A, andReusable[A]
is for ∃a∈A. In other words,Reusability[A]
provides reusability for typeA
and all its values, andReusable[A]
is a single value ofA
that is reusable.
Reusable functionsReusableFn[A, B]
are nowReusable[A => B]
. (TheA ~=> B
alias now reflects this so you won't need any change where the alias is used.) Instead of creating them viaReusableFn(…)
, it's nowReusable.fn(…)
.
-
Similar to the changes in
core
, there is a packagejapgolly.scalajs.react.test.raw
which contains the JS facades, with no Scala niceness, to React's test utilities. -
japgolly.scalajs.react.test.ReactTestUtils
is now a concrete object instead of a facade, and so all the additional utilities that were patched on with implicits earlier, are normal methods that live directly in the object - no more different behaviour depending on imports. -
Added
ReactTestUtils.{modify,replace}Props
which can be used to test a component when its parent changes and re-renders it. Great for testing hooks likecomponent{Will,Did}Update
,componentWillReceiveProps
,shouldComponentUpdate
. -
ReactTestVar
has been revised.- It now provides
StateSnapshot
(with and withoutReusability
) andStateAccess
. This is fantastic because instead of 3 separate utilities to test different types of code, there's now a single one that covers them all. Simple, easy to learn and use, less to remember, awesome. - It has an
onUpdate
hook. You'll usually use this to feed component updates back into itself. Example below.
- It now provides
-
Reduction:
ComponentTester
has been removed. Instead useReactTestUtils.withRenderedIntoDocument
followed by invocations ofReactTestUtils.{modify,replace}Props
. -
Reduction:
WithExternalCompStateAccess
has been removed. Instead create aReactTestVar
and use.stateAccess
and.onUpdate
. Example:val component: ScalaComponent[StateAccessPure[Int], Unit, Unit] = ... val testVar = ReactTestVar(1) ReactTestUtils.withRenderedIntoDocument(component(testVar.stateAccess)) { m => testVar.onUpdate(m.forceUpdate) // Update the component when it changes the state assert(m.outerHtmlScrubbed() == "<div>1</div>") Simulate.click(m.getDOMNode) // our example component calls .modState(_ + 1) onClick assert(testVar.value() == 2) assert(m.outerHtmlScrubbed() == "<div>2</div>") }
-
Cats module,
ext-cats
Where as before there was just optional support for Scalaz, there is now optional, analagous suppport for Cats. You're free to use one, the other, none or both.
(The
core
,extra
andtest
modules all get by with just plain 'ole Scala.) -
ScalaDoc
The ScalaDoc was pretty thin before, the reasoning being that all the documentation is on the project page under
doc/
. A number of people lamented its lack and so as of 1.0.0 there is now more ScalaDoc in the library. Over time, more will be added but I don't get paid for any work on this library unfortunately so please get involved if you'd like to see more.
As usual, there is a migration script that will update the majority of your code base. This time however, there are also a bunch of things you'll have to update manually.
Go to your project directory.
Make sure you've checked everything into version-control before you begin.
Then run:
curl -s https://raw.githubusercontent.com/japgolly/scalajs-react/master/doc/changelog/1.0.0-migrate.sh | bash
Alternatively you can open up 1.0.0-migrate.sh in your browser, inspect it, ensure it doesn't do anything nefaious or that you're uncomfortable with, then copy-paste it into your shell as a single command.
Make sure you've done the automated migration first.
These are the breaking changes that must be manually updated:
- Upgrade Scala.JS to 0.6.15
- If you're using any of these libraries, upgrade them to:
- Monocle 1.4.0
- ScalaCSS 0.5.3
- Scala Test-State 2.1.2
- VDOM changes
- See the VDOM cheatsheet.
(a: TagMod) + (b: TagMod) + …
becomesTagMod(a, b, …)
.div(x: Seq[_ <% TagMog])
becomesdiv(x.toTagMod)
. You can also usediv(x: _*)
but the Scala language will impose more usage restrictions. If you find yourself withdiv(x.map(y).toTagMod)
you can also change it todiv(x.toTagMod(y))
for brevity and efficiency.- The
?=
operator has been removed. Replacecondition ?= vdom
withvdom.when(condition)
orvdom.unless(condition)
. - The
:=
operator was overloaded before to handle optional cases (i.e.attr := Option[value]
). Change toattr :=? Option[value]
which is now consistent with the event operators-->
and==>
having-->?
and==>?
respectively. - Previously, the main package (
japgolly.scalajs.react
) had some vdom-related code in it. Now, all VDOM code is in the.vdom
package. This means that in cases where you aren't importing from.vdom
but are using certain VDOM features you'll need to add an import.// VdomElement not found import japgolly.scalajs.react.vdom.VdomElement // No implicit view available from Blah => VdomElement import japgolly.scalajs.react.vdom.Implicits._
- String-based refs (deprecated by React JS) have been removed. See REFS.md for how to use the new style refs.
- JS-based components now have first-class support. Old hacky code that had
casts to confusing types like
ReactComponentM_
will need to be replaced. See INTEROP.md. - The unpleasant
$
field on Scala component lifecycle hooks has now been removed. Use auto-complete to get what you need directly from the hook context.// So instead of this: .componentDidUpdate(ctx => doSomething(ctx.$.props, ctx.$.backend)) // it becomes something like this .componentDidUpdate(ctx => doSomething(ctx.currentProps, ctx.backend)) // If you really want to bypass the ctx and directly access the underlying component, // use one of the .mounted fields. .componentDidUpdate(ctx => ajaxThing(ctx.mountedImpure))
- Change custom mixins (that you use in
.configure
when creating Scala components):def install[P, S, B, N <: TopNode] = (_: ReactComponentB[P, S, B, N]).blah // Change ↑ to ↓ def install[P, C <: Children, S, B]: ScalaComponent.Config[P, C, S, B] = _.blah
- The implicit helpers
.tryFocus
(and its generalisation.tryTo
), have been removed. UseautoFocus := true
in your VDOM instead. CompStateAccess[S]
is now one of the following depending on what you're doing: (See TYPES.md)StateAccessPure[S]
StateAccessImpure[S]
StateAccess[F, S]
StateAccessor.*
.getDOMNode
is now consistent in that it obeys the effect type like everything else. Eg. if.props
and.state
returnCallback
s, then.getDOMNode
will too.- Mounted components'
.isMounted
now returnsOption[Boolean]
as not all components support it. It seems it's going to be deprecated and removed in React 16.
The migration script takes care of these but it's also worth highlighting these so you know:
- Events (
ReactEvent*
) have been renamed.SyntheticEvent[N]
⇒ReactEventFrom[N]
ReactEventI
⇒ReactEventFromInput
ReactEventH
⇒ReactEventFromHtml
ReactEventTA
⇒ReactEventFromTextArea
- VDOM -
Seriously, check out VDOM.md.
There are heaps of differences and a cheatsheet. CallbackB
alias removed. Just useCallbackTo[Boolean]
.ReusableFn(x).{set,mod}State
is nowReusable.fn.state(x).{set,mod}