Skip to content

Latest commit

 

History

History
250 lines (199 loc) · 12.2 KB

1.7.0.md

File metadata and controls

250 lines (199 loc) · 12.2 KB

1.7.0

This is a big release with big release notes. Grab a coffee and enjoy!

Firstly: A big chunk of this work was kindly sponsored by Gemini/NOIRLab/AURA c/o @cquiroz. A big thank you from myself and the community!

  • Scala.JS 1.0 support. We cross-build to retain support of Scala.JS 0.6.x. This will continue until the first upstream dependency drops support for 0.6.x.

  • Upgrade to React 16.13.1

    • Add ReactDOM.version
    • Add ReactDOMServer.version
    • Add ReactTestUtils.act(body)
    • Add ReactTestUtils.actAsync(body)
    • Add React.Profiler(id, callback)(children) -- see the React.Profiler doc for detail
    • Add React.Profiler.unstable_trace(name)(body)
    • New HTML attribute: disablePictureInPicture
    • Deprecated componentWillMount
    • Deprecated componentWillReceiveProps
    • Deprecated componentWillUpdate
  • React support

    • Add code: String to keyboard events
    • Added ReactFormEvent. It currently adds nothing over a basic event but it aligns with React's doc and they might decide to specialise it in future.
    • ReactDOMServer.render methods and ReactDOM.hydrate now accept VdomNodes instead of VdomElements
  • Introduction of ScalaJsReactConfig and the concept of global settings. Inside is DevOnly which houses settings that only affect fastOptJS and are removed from fullOptJS at compile-time.

  • The very first global dev-only setting is reusabilityOverride which allows you to override the behaviour of Reusability.shouldComponentUpdate. The most common use case is that during development you can call ReusabilityOverlay.overrideGloballyInDev() at the start of your JS main method which will provide a little GUI around all components with Reusability.shouldComponentUpdate that shows you when Reusability prevented updates, when it re-rendered, and why (either by hovering over the overlay, or clicking it to get a full log in the console). You can also call ScalaJsReactConfig.DevOnly.overrideReusability yourself and provide your own implementation if desirable.

  • Component names can now be generated automatically. You no longer need to specify them manually when creating a component. If you'd like to change all of your existing components to do this, there's a migration at the end of this page.

    For example the following component

    package com.github.example
    
    object Blah {
      val TopLevelComponent = ScalaComponent.builder[Unit]
        .renderBackend[Backend]
        .build
    }

    would be automatically named "com.github.example.Blah.TopLevel".

  • Automatic component naming trims "Comp" and "Component" from names by default. This behaviour can be overriden by calling the componentNameModifier* methods on ScalaJsReactConfig.

    For example, you might want to call ScalaJsReactConfig.componentNameModifierAppend(_.stripPrefix("com.github.example.")) to trim package names, resulting in the previous example being called "Blah.TopLevel".

  • Component names are now optionally elidable. Good for production builds.

    Specifically, if you add scalacOptions ++= Seq("-Xelide-below", "OFF") to your SBT, all of your component names will be removed from your output JS.

  • In the component builder

    • instead of calling initialState*, you can now call getDerivedStateFromProps[S](f: P => S) or getDerivedStateFromPropsAndState[S](f: (P, Option[S]) => S) (To be clear: you don't need to call getDerivedStateFromProps again later.)

      val Example = ScalaComponent.builder[Props]
        .getDerivedStateFromProps(_.text)
        .renderBackend[Backend]
        .build
    • renamed getDerivedStateFromProps(? => Option[S]) to getDerivedStateFromPropsOption

    • added getDerivedStateFromProps variant which accepts a ? => S

    • bugfix: calling getDerivedStateFromProps multiple times used to just take the first Some[S] result and discard subsequent calculations which is pretty silly in retrospect. It now runs them all sequentially chaining state updates of one, to the next.

  • VDOM changes

    • added VdomNode.static(vdom: VdomNode): VdomNode that wraps the given vdom in a component that always returns false from shouldComponentUpdate
    • added VdomElement.static(vdom: VdomElement): VdomElement that wraps the given vdom in a component that always returns false from shouldComponentUpdate
    • added .withOptionalRef that takes an Option, everywhere that .withRef exists
  • Bugfix: setState(newState: S, callback: Callback) wasn't calling the specified Callback (!!!)

  • Added async versions of all {set,mod}State functions. These return AsyncCallbacks that resume when React has finished applying the state change.

    .setStateAsync(S)                        : AsyncCallback[Unit]
    .modStateAsync(S => S)                   : AsyncCallback[Unit]
    .modStateAsync((S, P) => S)              : AsyncCallback[Unit]
    .setStateOptionAsync(Option[S])          : AsyncCallback[Unit]
    .modStateOptionAsync(S => Option[S])     : AsyncCallback[Unit]
    .modStateOptionAsync((S, P) => Option[S]): AsyncCallback[Unit]

    If you write a callback that must be executed after a state update, you would've learned that setState(s) >> c would be a bug because React applies state changes asynchronously. The correct way of writing it would be setState(s, c). With this change you could now also write setStateAsync(s) >> c.toAsyncCallback.

  • Made Callback(To) and AsyncCallback stack-safe

  • Added to AsyncCallback[A]:

    • def sync: CallbackTo[Either[AsyncCallback[A], A]] to turn a AsyncCallback into a synchronous Callback when possible
    • def unsafeRunNowSync(): A which is handy in unit tests where you've taken care to ensure that test code provides AsyncCallbacks that are actually synchronous under the hood. (eg. instead of a real async AJAX call your test provides an AsyncCallback that returns a test response immediately)
    • def delay[A](a: => A): AsyncCallback[A] and deprecate .point
    • versions of existing methods that AsyncCallback arguments, to take CallbackTo instances instead:
      def finallyRunSync      [B](runFinally: CallbackTo[B])                   : AsyncCallback[A]
      def flatMapSync         [B](f: A => CallbackTo[B])                       : AsyncCallback[B]
      def flatTapSync         [B](t: A => CallbackTo[B])                       : AsyncCallback[A]
      def flattenSync         [B](implicit ev: A => CallbackTo[B])             : AsyncCallback[B]
      def handleErrorSync        (f: Throwable => CallbackTo[A])               : AsyncCallback[A]
      def maybeHandleErrorSync   (f: PartialFunction[Throwable, CallbackTo[A]]): AsyncCallback[A]
    • more convenience methods. These just delegate to CallbackTo but having them on the AsyncCallback API helps discoverability in addition to convenience:
      def runNow       ()                       : Unit
      def setInterval  (dur: FiniteDuration)    : CallbackTo[Callback.SetIntervalResult]
      def setInterval  (dur: java.time.Duration): CallbackTo[Callback.SetIntervalResult]
      def setIntervalMs(ms: Double)             : CallbackTo[Callback.SetIntervalResult]
      def setTimeout   (dur: FiniteDuration)    : CallbackTo[Callback.SetTimeoutResult]
      def setTimeout   (dur: java.time.Duration): CallbackTo[Callback.SetTimeoutResult]
      def setTimeoutMs (ms: Double)             : CallbackTo[Callback.SetTimeoutResult]
  • Added Callback.throwException and deprecated Callback.error so that it's consistent with CallbackTo.throwException and AsyncCallback.throwException

  • Added CallbackOption.activeHtmlElement: CallbackOption[html.Element] which returns the currently focused HTML element (if there is one)

  • Added to StateSnapshot[S]:

    /** @return `None` if `value: S` isn't `value: T` as well. */
    def narrowOption[T <: S]: Option[StateSnapshot[T]]
    
    /** Unsafe because writes may be dropped.
      *
      * Eg. if you widen a `StateSnapshot[Banana]` into `StateSnapshot[Food]`,
      * calling `setState(banana2)` will work but `setState(pasta)` will be silently ignored.
      */
    def unsafeWiden[T >: S]: StateSnapshot[T]
    
    def zoomStateOption[T](f: S => Option[T])(g: T => S => S): Option[StateSnapshot[T]]
    
    def withReuse.zoomStateOption[T](optional: Reusable[(S => Option[T], T => S => S)]): Option[StateSnapshot[T]]
    
    // If using `import MonocleReact._`
    .zoomStateO          [B](o: monocle.Optional[A, B])          : Option[StateSnapshot[B]]
    .withReuse.zoomStateO[B](o: Reusable[monocle.Optional[A, B]]): Option[StateSnapshot[B]]
  • Added to StateSnapshot:

  • Changes in object Reusability:

    • update .caseClassExcept to accept String arguments instead of scala.Symbols, for which literals have been deprecated in Scala 2.13

    • added the following

      def asyncCallbackByRef  [A]   : Reusability[AsyncCallback[A]]
      def callbackByRef       [A]   : Reusability[CallbackTo[A]]
      def callbackOptionByRef [A]   : Reusability[CallbackOption[A]]
      def callbackKleisliByRef[A, B]: Reusability[CallbackKleisli[A, B]]
    • updated .deriveDebug and .caseClassExceptDebug to add a new option: logNonReuse: Boolean. When enabled, it will log details to the console when it encounters a non-reusable value.

      For example, if you used it to derive an instance for case class Person(id: Int, name: String, age: Int) and passed in Person(1, "Bob", 80) and Person(3, "Bob", 60) you would see this in your console:

      Instance of com.github.example.Person not-reusable for the following reasons:
        .id values not reusable
          A: 1
          B: 3
        .age values not reusable
          A: 80
          B: 60
  • Add to Reusable:

    • def callbackKleisliByRef[A, B](c: CallbackKleisli[A, B]): Reusable[CallbackKleisli[A, B]]
    • def asyncCallbackByRef[A](c: AsyncCallback[A]): Reusable[AsyncCallback[A]]
  • Add to Reusable[A]:

    • def <*[B](fb: Reusable[B]): Reusable[A] to combine reusability and return the left value
    • def *>[B](fb: Reusable[B]): Reusable[B] to combine reusability and return the right value
  • You can now (optionally) pass props to and through your Router. See the doc. Thanks @rpiaggio!

  • Testing

    • ReactTestUtils methods now only warn when failing to unmount a component, rather than throwing an error
    • Event simulation in tests now ensures that default properties of events are set.
      Example: you don't need to manually specify defaultPrevented or altKey in a Simulate.click, they now default to false.
  • MonocleReact

    • the modStateL and modStateOptionL methods now accept all kinds of appropriate optics instead of just lenses
  • Remove API deprecated prior to scalajs-react v1.5.0

  • Upgrade dependencies

    • Cats 2.1.1
    • Cats Effect 2.1.3
    • Monocle (Cats) 2.0.4
    • Monocle (Scalaz) 1.6.3
    • Scala 2.12.11 / 2.13.2
    • Scala.JS 0.6.32 / 1.0.1
    • scala-collection-compat 2.1.6
    • scalajs-dom 1.0.0
    • Scalaz 7.2.30

Migration

Avoiding deprecations:

find . -type f -name '*.scala' -exec perl -pi -e '
  s/\b(Callback[ \.]+)error\b/\1throwException/g;
  s/(?<=[( ,])'"'"'([a-z][A-Za-z0-9_]*)/"\1"/g if /Reusability.*caseClassExcept/;
  s/(AsyncCallback\s*(?:\.\s*)?)point\b/\1delay/g;
' {} +

Switching to automatically named components:

find . -type f -name '*.scala' -exec perl -pi -e '
  s/(ScalaComponent[ .]+builder.+\]) *\( *".*?" *\)/\1/;
  s/(ScalaComponent[ .]+static) *\( *".*?" *\)/\1/;
' {} +