All Downloads are FREE. Search and download functionalities are using the official Maven repository.

japgolly.scalajs.react.extra.StateSnapshotF.scala Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta12
Show newest version
package japgolly.scalajs.react.extra

import japgolly.scalajs.react.util.Effect._
import japgolly.scalajs.react.util.{DefaultEffects, NotAllowed}
import japgolly.scalajs.react.{Reusability, Reusable, StateAccess}
import scala.reflect.ClassTag

final class StateSnapshotF[F[_], A[_], S](val value: S,
                                          val underlyingSetFn: Reusable[StateSnapshotF.SetFn[F, S]],
                                          private[extra] val reusability: Reusability[S])
                                         (implicit FF: UnsafeSync[F], AA: Async[A]) extends StateAccess.Write[F, A, S] {

  override type WithEffect     [G[_]] = StateSnapshotF[G, A, S]
  override type WithAsyncEffect[G[_]] = StateSnapshotF[F, G, S]

  override def withEffect[G[_]](implicit G: UnsafeSync[G]): WithEffect[G] =
    G.subst[F, ({ type L[E[_]] = StateSnapshotF[E, A, S] })#L](this) {
      val setFn2: Reusable[StateSnapshotF.SetFn[G, S]] =
        underlyingSetFn.map(f => (a, c) => G.transSync(f(a, F.transSync(c)))(F))
      new StateSnapshotF(value, setFn2, reusability)(G, A)
    }(F)

  override def withAsyncEffect[G[_]](implicit G: Async[G]): WithAsyncEffect[G] =
    new StateSnapshotF(value, underlyingSetFn, reusability)(F, G)

  override protected implicit def F: UnsafeSync[F] = FF
  override protected implicit def A: Async[A] = AA

  override def toString = s"StateSnapshot($value)"

  /** @param callback Executed regardless of whether state is changed. */
  override def setStateOption[G[_]](newState: Option[S], callback: => G[Unit])(implicit G: Dispatch[G]): F[Unit] =
    underlyingSetFn(newState, F.transDispatch(callback))

  /** @param callback Executed regardless of whether state is changed. */
  override def modStateOption[G[_]](mod: S => Option[S], callback: => G[Unit])(implicit G: Dispatch[G]): F[Unit] =
    setStateOption(mod(value), callback)

  private def copyWithoutReuse[T](value: T)(set: StateSnapshotF.SetFn[F, T]): StateSnapshotF[F, A, T] =
    new StateSnapshotF[F, A, T](value, Reusable.byRef(set), Reusability.never)(F, A)

  /** THIS WILL VOID REUSABILITY.
    *
    * The resulting `StateSnapshot[T]` will not be reusable.
    */
  def xmapState[T](f: S => T)(g: T => S): StateSnapshotF[F, A, T] =
    copyWithoutReuse(f(value))((ot, cb) => underlyingSetFn.value(ot map g, cb))

  /** THIS WILL VOID REUSABILITY.
    *
    * The resulting `StateSnapshot[T]` will not be reusable.
    */
  def zoomState[T](f: S => T)(g: T => S => S): StateSnapshotF[F, A, T] =
    xmapState(f)(g(_)(value))

  /** THIS WILL VOID REUSABILITY.
    *
    * The resulting `StateSnapshot[T]` will not be reusable.
    */
  def zoomStateOption[T](f: S => Option[T])(g: T => S => S): Option[StateSnapshotF[F, A, T]] =
    f(value).map(t => copyWithoutReuse(t)((ot, cb) => underlyingSetFn.value(ot.map(g(_)(value)), cb)))

  def withReuse: StateSnapshotF.InstanceMethodsWithReuse[F, A, S] =
    new StateSnapshotF.InstanceMethodsWithReuse(this)

  /** @return `None` if `value: S` isn't `value: T` as well. */
  def narrowOption[T <: S: ClassTag]: Option[StateSnapshotF[F, A, T]] =
    value match {
      case t: T => Some(copyWithoutReuse(t)(setStateOption(_, _)(F)))
      case _    => None
    }

  /** 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](implicit ct: ClassTag[S]): StateSnapshotF[F, A, T] =
    copyWithoutReuse(value: T) { (ob, cb) =>
      val oa: Option[S] =
        ob match {
          case Some(s: S) => Some(s)
          case _          => None
        }
      setStateOption(oa, cb)(F)
    }

  /** THIS WILL VOID REUSABILITY.
    *
    * The resulting `StateSnapshotF[F, A, S]` will not be reusable.
    */
  def withValue(s: S): StateSnapshotF[F, A, S] =
    copyWithoutReuse(s)(underlyingSetFn)
}

// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████

object StateSnapshotF {
  type SetFn      [F[_], -S] = (Option[S]     , F[Unit])   => F[Unit]
  type ModFn      [F[_],  S] = (S => Option[S], F[Unit])   => F[Unit]
  type TupledSetFn[F[_], -S] = ((Option[S]     , F[Unit])) => F[Unit]
  type TupledModFn[F[_],  S] = ((S => Option[S], F[Unit])) => F[Unit]

  type StateSnapshot[S] = StateSnapshotF[DefaultEffects.Sync, DefaultEffects.Async, S]

  private[this] val reusabilityInstance: Reusability[StateSnapshotF[Nothing, Nothing, Any]] = {
    val f = implicitly[Reusability[Reusable[SetFn[Nothing, Any]]]] // Safe to reuse
    Reusability((x, y) =>
      (x eq y) ||
        (f.test(x.underlyingSetFn, y.underlyingSetFn)
          && x.reusability.test(x.value, y.value)
          && y.reusability.test(x.value, y.value)))
  }

  implicit def reusability[F[_], A[_], S]: Reusability[StateSnapshotF[F, A, S]] =
    reusabilityInstance.asInstanceOf[Reusability[StateSnapshotF[F, A, S]]]

  final class InstanceMethodsWithReuse[F[_], A[_], S](self: StateSnapshotF[F, A, S]) { // not AnyVal, nominal for Monocle ext

    def withValue(s: S)(implicit r: Reusability[S]): StateSnapshotF[F, A, S] =
      new StateSnapshotF(s, self.underlyingSetFn, r)(self.F, self.A)

    @deprecated("This ability doesn't work. See https://github.com/japgolly/scalajs-react/issues/721 for an explanation, and https://japgolly.github.io/scalajs-react/#examples/state-snapshot-2 for the alternative.", "1.7.1")
    def xmapState(no: NotAllowed) = no.result

    @deprecated("This ability doesn't work. See https://github.com/japgolly/scalajs-react/issues/721 for an explanation, and https://japgolly.github.io/scalajs-react/#examples/state-snapshot-2 for the alternative.", "1.7.1")
    def zoomState(no: NotAllowed) = no.result

    @deprecated("This ability doesn't work. See https://github.com/japgolly/scalajs-react/issues/721 for an explanation, and https://japgolly.github.io/scalajs-react/#examples/state-snapshot-2 for the alternative.", "1.7.1")
    def zoomStateOption(no: NotAllowed) = no.result
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy