Permalink
Fetching contributors…
Cannot retrieve contributors at this time
257 lines (202 sloc) 9.78 KB

v1.2.0

A big thanks to my recent patrons who bumped this release up my personal-time priorities and provided significant motivation (through my gratitude) to get through all the work this release needed.

Highlights

  • React 16 support and adherence
  • New shapes for setState & modState, and their propagation all over
  • Type-safe and purely-functional AJAX
  • Type-safe and purely-functional refs
  • detailed error messages for invalid JS components

(Don't miss the migration section below!)

Detail

  • [React 16] New render return types

    • Strings
    • Numbers
    • EmptyVdom
    • Option[_]
    • VdomArray
    • ReactFragment
    • ReactPortal
  • [React 16] keys can no longer be booleans

  • [React 16] Everywhere that there was setState(S) and modState(S => S), there is now setStateOption(Option[S]) and modStateOption(S => Option[S]).

  • [React v?] Everywhere that there was modState(S => S), there is now modState((S, P) => S) to use the current component props value when calculating a state change. (I'm not sure in which React release this was introduced; it was either undocumented or I accidentally missed it.)

  • [React 16] Add new lifecycle method: componentDidCatch. (read about this! It's important. As of React 16 errors bubble up through your component tree and will unmount your entire app if unhandled.)

  • [React 16] .getDOMNode now return Either[dom.Text | dom.Element] instead of just dom.Element. Implicit methods have been added: .to{Element,Text} to return an Option[dom.{Element,Text}], and .as{Element,Text} to return an dom.{Element,Text} and throw otherwise.

  • [React 16] The .raw packages have been migrated to the new types of React 16 and reorganised to match their JS counterparts. Examples:

    • .raw.ReactChildren => .raw.React.Children
    • .raw.ReactComponentEs6 => .raw.React.Component
    • .raw.ReactNode => .raw.React.Node
    • etc
  • VDOM changes:

    • EmptyVdom is now a VdomNode instead of a TagMod. If you want the old behaviour, use TagMod.empty
    • New constructors:
      • ReactFragment(ns: VdomNode*)
      • ReactFragment.withKey(key: Key)(ns: VdomNode*)
      • ReactPortal(child: VdomNode, container: DomContainer)
    • New attributes:
      • hidden
      • on
    • The following attributes are now boolean-only:
      • async
      • controls
      • default
      • defer
      • formNoValidate
      • itemScope
      • loop
      • multiple
      • muted
      • noValidate
      • open
      • reserved
      • scoped
      • seamless
      • selected
  • Added new classes that you can use to pass around a single function that does setState or modState respectively, with downstream callers able to choose the variation they need. (Eg. setState(s) or setState(Option(s), cb))

    SetStateFn[F, S]
    ModStateFn[F, S]
    ModStateWithPropsFn[F, P, S]
    
    // And aliases:
    SetStateFnPure[S]
    SetStateFnImpure[S]
    ModStateFnPure[S]
    ModStateFnImpure[S]
    ModStateWithPropsFnPure[P, S]
    ModStateWithPropsFnImpure[P, S]
  • StateAccessor typeclasses now support all the …{set,mod}State… methods that StateAccess provides, including the optional Callback args and the new {set,mod}StateOption methods. This has required a slight change to typeclass usage syntax: instead of stateAccessor.setState($)(s) it's now stateAccessor($).setState(s, [cb]).

  • StateSnapshot now supports all the …{set,mod}State… methods that StateAccess provides, including the optional Callback args and the new {set,mod}StateOption methods. Most of the DSL to create StateSnapshots is unchanged but if you're creating StateSnapshots manually by passing in functions then you'll need to change then from S => Callback to (Option[S], Callback) => Callback or SetStateFn[S]. (See ReuseExample for an example)

  • Add a purely-functional Callback-integrated Ajax helper to the extra module. (demo here)

  • Much more detailed error messages when you try to create JS components with invalid arguments. Sample:

    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    Invalid JsComponent! You've called JsComponent(undefined)
    Source: com.whatever.finance.money.wealth.MoneyMaker.Component (line #37)
    
    Make sure that
      * your @JSImport / @JSGlobal annotations have the correct values
      * the JS that you're referencing has been loaded into the JS environment
    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    
  • Add CallbackKleisli[A, B] (aka Kleisli[CallbackTo, A, B] aka ReaderT[A, CallbackTo, B], definition: A => CallbackTo[B]) to core module.

  • Add to object Reusable

    • callbackByRef[A](c: CallbackTo[A]): Reusable[CallbackTo[A]]
    • callbackOptionByRef[A](c: CallbackOption[A]): Reusable[CallbackOption[A]]
    • byRefIso[A, B <: AnyRef](a: A)(iso: A => B): Reusable[A] - Compare by reference through an isomorphism
  • Added Reusability#logNonReusable to log a warning when instance is non-reusable

  • Add Px.ManualCollection which is a little add-only mutable collection of manual Px thunks, with a refresh function that refreshes everything in the collection at once. Idea is that you create and store it in your component backend, register Pxs as you create them, then call refresh() in your render function.

  • Added type class instances:

    • cats.arrow.Arrow[CallbackKleisli]
    • cats.arrow.Choice[CallbackKleisli]
    • cats.MonadError[CallbackKleisli[A, ?], Throwable]
    • cats.MonadError[CallbackTo, Throwable]
    • scalaz.Arrow[CallbackKleisli]
    • scalaz.BindRec[CallbackKleisli[A, ?]]
    • scalaz.Choice[CallbackKleisli]
    • scalaz.Distributive[CallbackKleisli[A, ?]]
    • scalaz.MonadError[CallbackKleisli[A, ?], Throwable]
    • scalaz.MonadError[CallbackTo, Throwable]
    • scalaz.MonadPlus[CallbackOption]
    • scalaz.MonadReader[CallbackKleisli[A, ?], A]
  • Added ext-monocle-cats module with the same API as ext-monocle but using the Cats version of Monocle.

  • Removed

    • CallbackOption#get - deprecated in 1.0.1
    • CallbackOption#toBoolCB - deprecated in 1.0.1
    • .isMounted() - removed by React
    • ReactAddons - No longer has any content
      • CSSTransitionGroup - React have moved this into its own separate library
      • Perf - React have removed this entirely in React 16
    • ScalaComponent.Builder#buildWithReactCreateClass - React.createClass was removed in React 16
    • WebpackRequire - deprecated in 1.0.1
  • Upgrades

    • Cats 1.0.1
    • Monocle 1.5.0
    • Scala 2.11.12
    • Scala.JS 0.6.22
    • Scala.JS DOM 0.9.4
    • Scalaz 7.2.20

Migration

  • EmptyVdom is now a VdomNode instead of a TagMod. If an empty TagMod is actually what you want use TagMod.empty instead.

  • Refs have are now pure, type-safe, and NPE safe. Migrate with:

    find . -name '*.scala' -type f -exec perl -pi -e 's/((Js|Scala)Component)[ .]+mutableRefTo/Ref.to$1/g' {} +
    find . -name '*.scala' -type f -exec perl -pi -e 's/(?<=[ .])ref\( *([a-zA-Z0-9_]+) += +_ *\)/withRef($1)/g' {} +
    find . -name '*.scala' -type f -exec perl -pi -e 's/^( *(?:private\S*|protected)? +)var +([a-zA-Z0-9_]+) *: *(((html|svg|dom)\.|(HTML|SVG))[a-zA-Z0-9_.]+) += +(?:_|null) *$/$1val $2 = Ref[$3]/' {} +

    Usage changes are as follows:

       class Backend($: BackendScope[Unit, String]) {
    
    -    var inputRef: html.Input = _
    +    val inputRef = Ref[html.Input]
    
         def handleChange(e: ReactEventFromInput) =
           $.setState(e.target.value)
    
         def clearAndFocusInput() =
    -      $.setState("", Callback(inputRef.focus()))
    +      $.setState("", inputRef.foreach(_.focus()))
    
         def render(state: String) =
           <.div(
             <.div(
               ^.onClick --> clearAndFocusInput,
               "Click to Focus and Reset"),
             <.input(
               ^.value     := state,
               ^.onChange ==> handleChange)
    -          .ref(inputRef = _)
    +          .withRef(inputRef)
           )
       }
       val scalaRef =
    -    ScalaComponent.mutableRefTo(MyScalaComponent)
    +    Ref.toScalaComponent(MyScalaComponent)
    
       val jsRef =
    -    JsComponent.mutableRefTo(MyJsComponent)
    +    Ref.toJsComponent(MyJsComponent)

    In your unit tests you often just want to get the ref value without worrying about safety; for that case, call .unsafeGet() on your ref.

  • .getDOMNode used to return a dom.Element but now returns an Either[dom.Text, dom.Element] because that's what React 16 now returns. .getDOMNode.map(_.asElement) and .getDOMNode.asElement are replacements for .getDOMNode in the pure/CallbackTo and impure/Id flavours respectively.

    It's usually the pure version in main code and the impure one in unit tests so use the following to migration commands to help you along:

    # .getDOMNode => .getDOMNode.map(.asElement)
    find . -name '*.scala' -type f -exec perl -pi -e 's/(?<![a-zA-Z0-9_])getDOMNode(?![a-zA-Z0-9_])(?![. ]dom(?:as|To)Html)(?![. ]domCast)/getDOMNode.map(.asElement)/g' {} +
    
    # .getDOMNode => .getDOMNode.asElement
    find . -name '*.scala' -type f -exec perl -pi -e 's/(?<![a-zA-Z0-9_])getDOMNode(?![a-zA-Z0-9_])(?![. ]dom(?:as|To)Html)(?![. ]domCast)/getDOMNode.asElement/g' {} +
  • If you use Scala Test-State, you'll need to upgrade to 2.1.3.

Support

If you like what I do —my OSS libraries, my contributions to other OSS libs, my programming blog— and you'd like to support me, more content, more lib maintenance, please become a patron! I do all my OSS work unpaid so showing your support will make a big difference.