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

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

// 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 cats.kernel.Monoid
import cats.syntax.all.*
import crystal.Pot
import crystal.react.*
import japgolly.scalajs.react.*
import japgolly.scalajs.react.hooks.CustomHook
import japgolly.scalajs.react.hooks.Hooks.UseEffectArg
import japgolly.scalajs.react.util.DefaultEffects.Async as DefaultA

object UseEffectWhenDepsReady:
  // Provides functionality for all the flavors
  private def hookBuilder[D, A: UseEffectArg: Monoid, R: Reusability](deps: => Pot[D])(
    effect:  D => A,
    reuseBy: Option[R]
  ): HookResult[Unit] =
    useEffectWithDeps(reuseBy): _ =>
      deps.map(effect).toOption.orEmpty

  /**
   * Effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not when they
   * change once `Ready`). For multiple dependencies, use `(par1, par2, ...).tupled`. Dependencies
   * are passed unpacked to the effect bulding function.
   */
  final inline def useEffectWhenDepsReady[D, A: UseEffectArg: Monoid](
    deps: => Pot[D]
  )(effect: D => A): HookResult[Unit] =
    hookBuilder(deps)(effect, deps.toOption.void)

  /**
   * Effect that runs when `Pot` dependencies transition into a `Ready` state or change once
   * `Ready`. For multiple dependencies, use `(par1, par2, ...).tupled`. Dependencies are passed
   * unpacked to the effect bulding function.
   */
  final inline def useEffectWhenDepsReadyOrChange[D: Reusability, A: UseEffectArg: Monoid](
    deps: => Pot[D]
  )(effect: D => A): HookResult[Unit] =
    hookBuilder(deps)(effect, deps.toOption)

  // Provides functionality for all the flavors
  private def asyncHookBuilder[D, G, R: Reusability](deps: => Pot[D])(
    effect:  D => G,
    reuseBy: Option[R]
  )(using EffectWithCleanup[G, DefaultA]): HookResult[Unit] =
    useAsyncEffectWithDeps(reuseBy): _ =>
      deps.map(effect(_).normalize).toOption.orEmpty

  /**
   * Effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not when they
   * change once `Ready`). For multiple dependencies, use `(par1, par2, ...).tupled`. Dependencies
   * are passed unpacked to the effect bulding function.
   */
  final inline def useAsyncEffectWhenDepsReady[D, G](
    deps: => Pot[D]
  )(effect: D => G)(using EffectWithCleanup[G, DefaultA]): HookResult[Unit] =
    asyncHookBuilder(deps)(effect, deps.toOption.void)

  /**
   * Effect that runs when `Pot` dependencies transition into a `Ready` state or change once
   * `Ready`. For multiple dependencies, use `(par1, par2, ...).tupled`. Dependencies are passed
   * unpacked to the effect bulding function.
   */
  final inline def useAsyncEffectWhenDepsReadyOrChange[D: Reusability, G](
    deps: => Pot[D]
  )(effect: D => G)(using EffectWithCleanup[G, DefaultA]): HookResult[Unit] =
    asyncHookBuilder(deps)(effect, deps.toOption)

  // *** The rest is to support builder-style hooks *** //

  private def hook[D, A: UseEffectArg: Monoid, R: Reusability]
    : CustomHook[WithPotDeps[D, A, R], Unit] =
    CustomHook.fromHookResult(input => hookBuilder(input.deps)(input.fromDeps, input.reuseValue))

  private def asyncHook[G, D, R: Reusability](using
    EffectWithCleanup[G, DefaultA]
  ): CustomHook[WithPotDeps[D, G, R], Unit] =
    CustomHook.fromHookResult: input =>
      asyncHookBuilder(input.deps)(input.fromDeps, input.reuseValue)

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

      /**
       * Effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not when
       * they change once `Ready`). For multiple dependencies, use `(par1, par2, ...).tupled`.
       * Dependencies are passed unpacked to the effect bulding function.
       */
      final def useEffectWhenDepsReadyBy[D, A: UseEffectArg: Monoid](
        deps: Ctx => Pot[D]
      )(effect: Ctx => D => A)(using
        step: Step
      ): step.Self =
        api.customBy { ctx =>
          val hookInstance = hook[D, A, Unit]
          hookInstance(WithPotDeps.WhenReady(deps(ctx), effect(ctx)))
        }

      /**
       * Effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not when
       * they change once `Ready`). For multiple dependencies, use `(par1, par2, ...).tupled`.
       * Dependencies are passed unpacked to the effect bulding function.
       */
      final def useEffectWhenDepsReady[D, A: UseEffectArg: Monoid](
        deps: => Pot[D]
      )(effect: D => A)(using
        step: Step
      ): step.Self =
        useEffectWhenDepsReadyBy(_ => deps)(_ => effect)

      /**
       * Effect that runs when `Pot` dependencies transition into a `Ready` state or change once
       * `Ready`. For multiple dependencies, use `(par1, par2, ...).tupled`. Dependencies are passed
       * unpacked to the effect bulding function.
       */
      final def useEffectWhenDepsReadyOrChangeBy[D: Reusability, A: UseEffectArg: Monoid](
        deps: Ctx => Pot[D]
      )(effect: Ctx => D => A)(using
        step: Step
      ): step.Self =
        api.customBy { ctx =>
          val hookInstance = hook[D, A, D]
          hookInstance(WithPotDeps.WhenReadyOrChange(deps(ctx), effect(ctx)))
        }

      /**
       * Effect that runs when `Pot` dependencies transition into a `Ready` state or change once
       * `Ready`. For multiple dependencies, use `(par1, par2, ...).tupled`. Dependencies are passed
       * unpacked to the effect bulding function.
       */
      final def useEffectWhenDepsReadyOrChangeBy[D: Reusability, A: UseEffectArg: Monoid](
        deps: => Pot[D]
      )(effect: D => A)(using
        step: Step
      ): step.Self =
        useEffectWhenDepsReadyOrChangeBy(_ => deps)(_ => effect)

      /**
       * Async effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not
       * when they change once `Ready`) and returns a cleanup callback. For multiple dependencies,
       * use `(par1, par2, ...).tupled`. Dependencies are passed unpacked to the effect bulding
       * function.
       */
      final def useAsyncEffectWhenDepsReady[G, D](deps: => Pot[D])(effect: D => G)(using
        step: Step,
        G:    EffectWithCleanup[G, DefaultA]
      ): step.Self =
        useAsyncEffectWhenDepsReadyBy(_ => deps)(_ => effect)

      /**
       * Async effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not
       * when they change once `Ready`) and returns a cleanup callback. For multiple dependencies,
       * use `(par1, par2, ...).tupled`. Dependencies are passed unpacked to the effect bulding
       * function.
       */
      final def useAsyncEffectWhenDepsReadyBy[G, D](
        deps: Ctx => Pot[D]
      )(effect: Ctx => D => G)(using
        step: Step,
        G:    EffectWithCleanup[G, DefaultA]
      ): step.Self =
        api.customBy { ctx =>
          val hookInstance = asyncHook[G, D, Unit]
          hookInstance(WithPotDeps.WhenReady(deps(ctx), effect(ctx)))
        }

      /**
       * Async effect that runs when `Pot` dependencies transition into a `Ready` state or change
       * once `Ready` and returns a cleanup callback. For multiple dependencies, use `(par1, par2,
       * ...).tupled`. Dependencies are passed unpacked to the effect bulding function.
       */
      final def useAsyncEffectWhenDepsReadyOrChange[G, D: Reusability](
        deps: => Pot[D]
      )(effect: D => G)(using
        step: Step,
        G:    EffectWithCleanup[G, DefaultA]
      ): step.Self =
        useAsyncEffectWhenDepsReadyOrChangeBy(_ => deps)(_ => effect)

      /**
       * Async effect that runs when `Pot` dependencies transition into a `Ready` state or change
       * once `Ready` and returns a cleanup callback. For multiple dependencies, use `(par1, par2,
       * ...).tupled`. Dependencies are passed unpacked to the effect bulding function.
       */
      final def useAsyncEffectWhenDepsReadyOrChangeBy[G, D: Reusability](
        deps: Ctx => Pot[D]
      )(effect: Ctx => D => G)(using
        step: Step,
        G:    EffectWithCleanup[G, DefaultA]
      ): step.Self =
        api.customBy { ctx =>
          val hookInstance = asyncHook[G, D, D]
          hookInstance(WithPotDeps.WhenReadyOrChange(deps(ctx), effect(ctx)))
        }
    }

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

      /**
       * Effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not when
       * they change once `Ready`). For multiple dependencies, use `(par1, par2, ...).tupled`.
       * Dependencies are passed unpacked to the effect bulding function.
       */
      def useEffectWhenDepsReadyBy[D, A: UseEffectArg: Monoid](
        deps: CtxFn[Pot[D]]
      )(effect: CtxFn[D => A])(using
        step: Step
      ): step.Self =
        useEffectWhenDepsReadyBy(step.squash(deps)(_))(step.squash(effect)(_))

      /**
       * Effect that runs when `Pot` dependencies transition into a `Ready` state or change once
       * `Ready`. For multiple dependencies, use `(par1, par2, ...).tupled`. Dependencies are passed
       * unpacked to the effect bulding function.
       */
      def useEffectWhenDepsReadyOrChangeBy[D: Reusability, A: UseEffectArg: Monoid](
        deps: CtxFn[Pot[D]]
      )(effect: CtxFn[D => A])(using
        step: Step
      ): step.Self =
        useEffectWhenDepsReadyOrChangeBy(step.squash(deps)(_))(step.squash(effect)(_))

      /**
       * Async effect that runs whenever `Pot` dependencies transition into a `Ready` state (but not
       * when they change once `Ready`) and returns a cleanup callback. For multiple dependencies,
       * use `(par1, par2, ...).tupled`. Dependencies are passed unpacked to the effect bulding
       * function.
       */
      def useAsyncEffectWhenDepsReadyBy[G, D](deps: CtxFn[Pot[D]])(effect: CtxFn[D => G])(using
        step: Step,
        G:    EffectWithCleanup[G, DefaultA]
      ): step.Self =
        useAsyncEffectWhenDepsReadyBy(step.squash(deps)(_))(step.squash(effect)(_))

      /**
       * Async effect that runs when `Pot` dependencies transition into a `Ready` state or change
       * once `Ready` and returns a cleanup callback. For multiple dependencies, use `(par1, par2,
       * ...).tupled`. Dependencies are passed unpacked to the effect bulding function.
       */
      def useAsyncEffectWhenDepsReadyOrChangeBy[G, D: Reusability](
        deps: CtxFn[Pot[D]]
      )(effect: CtxFn[D => G])(using
        step: Step,
        G:    EffectWithCleanup[G, DefaultA]
      ): step.Self =
        useAsyncEffectWhenDepsReadyOrChangeBy(step.squash(deps)(_))(step.squash(effect)(_))
    }
  }

  protected trait HooksApiExt {
    import HooksApiExt.*

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

    implicit def hooksEffectWhenDepsReady2[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 - 2025 Weber Informatics LLC | Privacy Policy