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

crystal.react.hooks.UseStateCallback.scala Maven / Gradle / Ivy

There is a newer version: 0.45.2
Show newest version
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package crystal.react.hooks

import japgolly.scalajs.react.*
import japgolly.scalajs.react.hooks.CustomHook
import japgolly.scalajs.react.hooks.Hooks
import japgolly.scalajs.react.util.DefaultEffects.Sync as DefaultS

import scala.collection.immutable.Queue

object UseStateCallback {
  def hook[A] =
    CustomHook[Hooks.UseState[A]]
      .useRef(Queue.empty[A => DefaultS[Unit]])
      // Credit to japgolly for this implementation; this is copied from StateSnapshot.
      .useEffectBy: (state, delayedCallbacks) =>
        val cbs = delayedCallbacks.value
        if (cbs.isEmpty)
          DefaultS.empty
        else
          delayedCallbacks.set(Queue.empty) >>
            DefaultS.runAll(cbs.toList.map(_(state.value))*)
      .useCallbackBy: (_, delayedCallbacks) =>
        (cb: A => DefaultS[Unit]) => delayedCallbacks.mod(_.enqueue(cb))
      .buildReturning: (_, _, onNextStateChange) =>
        onNextStateChange

  object HooksApiExt {
    sealed class Primary[Ctx, Step <: HooksApi.AbstractStep](api: HooksApi.Primary[Ctx, Step]) {

      /**
       * Given a state, allows registering callbacks which are triggered when the state changes.
       */
      final def useStateCallback[A](state: => Hooks.UseState[A])(using
        step: Step
      ): step.Next[Reusable[(A => DefaultS[Unit]) => DefaultS[Unit]]] =
        useStateCallbackBy(_ => state)

      /**
       * Given a state, allows registering callbacks which are triggered when the state changes.
       */
      final def useStateCallbackBy[A](state: Ctx => Hooks.UseState[A])(using
        step: Step
      ): step.Next[Reusable[(A => DefaultS[Unit]) => DefaultS[Unit]]] =
        api.customBy { ctx =>
          val hookInstance = hook[A]
          hookInstance(state(ctx))
        }
    }

    final class Secondary[Ctx, CtxFn[_], Step <: HooksApi.SubsequentStep[Ctx, CtxFn]](
      api: HooksApi.Secondary[Ctx, CtxFn, Step]
    ) extends Primary[Ctx, Step](api) {

      /**
       * Given a state, allows registering callbacks which are triggered when the state changes.
       */
      def useStateCallbackBy[A](state: CtxFn[Hooks.UseState[A]])(using
        step: Step
      ): step.Next[Reusable[(A => DefaultS[Unit]) => DefaultS[Unit]]] =
        useStateCallbackBy(step.squash(state)(_))
    }
  }

  protected trait HooksApiExt {
    import HooksApiExt._

    implicit def hooksExtDelayedCallback1[Ctx, Step <: HooksApi.AbstractStep](
      api: HooksApi.Primary[Ctx, Step]
    ): Primary[Ctx, Step] =
      new Primary(api)

    implicit def hooksExtDelayedCallback2[Ctx, CtxFn[_], Step <: HooksApi.SubsequentStep[Ctx,
                                                                                         CtxFn
    ]](
      api: HooksApi.Secondary[Ctx, CtxFn, Step]
    ): Secondary[Ctx, CtxFn, Step] =
      new Secondary(api)
  }

  object syntax extends HooksApiExt
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy