Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impement WhenDepsReadyOrChange versions #650

Merged
merged 2 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,19 +204,28 @@ Version of `useSerialState` that returns a `Reuse[View[A]]`.

### useEffectWhenDepsReady

Version of `useEffect` applicable only when there's a unique dependency of type `Pot[A]`. It will trigger the effect only when the dependency transitions to a `Ready` state, whether it was from `Pending` or `Error`.
Version of `useEffect` applicable only when there's a unique dependency of type `Pot[A]`. It will trigger the effect only when the dependency transitions to a `Ready` state, whether it was from `Pending` or `Error`. There are also `WhenDepsReadyOrChange` versions, which will also trigger if the dependencies change value once in `Ready` state.

Note that multiple `Pot` dependencies can be combined into one with `.tupled`.

``` scala
useEffectWhenDepsReady[D](deps: => Pot[D])(effect: D => Callback)
useEffectWhenDepsReadyBy[D](deps: Ctx => Pot[D])(effect: Ctx => D => Callback)

useEffectWhenDepsReadyOrChange[D: Reusability](deps: => Pot[D])(effect: D => Callback)
useEffectWhenDepsReadyOrChangeBy[D: Reusability](deps: Ctx => Pot[D])(effect: Ctx => D => Callback)

useEffectWhenDepsReady[D](deps: => Pot[D])(effect: D => IO[Unit])
useEffectWhenDepsReadyBy[D](deps: Ctx => Pot[D])(effect: Ctx => D => IO[Unit])

useEffectWhenDepsReadyOrChange[D: Reusability](deps: => Pot[D])(effect: D => IO[Unit])
useEffectWhenDepsReadyOrChangeBy[D: Reusability](deps: Ctx => Pot[D])(effect: Ctx => D => IO[Unit])

useEffectWhenDepsReady[D](deps: => Pot[D])(effect: D => CallbackTo[Callback]) // return cleanup
useEffectWhenDepsReadyBy[D](deps: Ctx => Pot[D])(effect: Ctx =>D => CallbackTo[Callback]) // return cleanup

useEffectWhenDepsReadyOrChange[D: Reusability](deps: => Pot[D])(effect: D => CallbackTo[Callback]) // return cleanup
useEffectWhenDepsReadyOrChangeBy[D: Reusability](deps: Ctx => Pot[D])(effect: Ctx =>D => CallbackTo[Callback]) // return cleanup
```

### useAsyncEffect
Expand Down Expand Up @@ -245,6 +254,11 @@ Also allows returning a cleanup effect, which `useEffect` only supports when use
useAsyncEffectWhenDepsReady[D](deps: => Pot[D])(effect: D => IO[IO[Unit]]) // return a cleanup effect
useAsyncEffectWhenDepsReadyBy[D](deps: Ctx => Pot[D])(effect: Ctx => D => IO[Unit])
useAsyncEffectWhenDepsReadyBy[D](deps: Ctx => Pot[D])(effect: Ctx => D => IO[IO[Unit]]) // return a cleanup effect

useAsyncEffectWhenDepsReadyOrChange[D: Reusability](deps: => Pot[D])(effect: D => IO[Unit])
useAsyncEffectWhenDepsReadyOrChange[D: Reusability](deps: => Pot[D])(effect: D => IO[IO[Unit]]) // return a cleanup effect
useAsyncEffectWhenDepsReadyOrChangeBy[D: Reusability](deps: Ctx => Pot[D])(effect: Ctx => D => IO[Unit])
useAsyncEffectWhenDepsReadyOrChangeBy[D: Reusability](deps: Ctx => Pot[D])(effect: Ctx => D => IO[IO[Unit]]) // return a cleanup effect
```


Expand All @@ -256,7 +270,9 @@ Note that all versions either have dependencies or are executed `onMount`. It do

Also note that when dependencies change, the hook value will revert to `Pending` until the new effect completes. If this is undesireable, there are `useEffectKeepResult*` variants which will instead keep the hook value as `Ready(oldValue)` until the new effect completes.

There are also `WhenDepsReady` versions, which will only execute the effect when dependencies are ready. If they change or transition to `Pending` or `Error`, then the result will revert to `Pending`. However, if the `KeepResult` version is used, it will retain the last value.
There are also `WhenDepsReady` versions, which will only execute the effect when dependencies transition to `Ready`. If they transition to `Pending` or `Error`, then the result will revert to `Pending`. However, if the `KeepResult` version is used, it will retain the last value.

Furthermore, there are also `WhenDepsReadyOrChange` versions, which will only execute the effect when dependencies transition to `Ready` or change value once `Ready`. If they change or transition to `Pending` or `Error`, then the result will revert to `Pending`. However, if the `KeepResult` version is used, it will retain the last value.


``` scala
Expand All @@ -272,8 +288,14 @@ There are also `WhenDepsReady` versions, which will only execute the effect when
useEffectResultWhenDepsReady[D: Reusability, A](deps: => D)(effect: D => IO[A]): Pot[A]
useEffectResultWhenDepsReadyBy[D: Reusability, A](deps: Ctx => D)(effect: Ctx => D => IO[A]): Pot[A]

useEffectResultWhenDepsReadyOrChange[D: Reusability, A](deps: => D)(effect: D => IO[A]): Pot[A]
useEffectResultWhenDepsReadyOrChangeBy[D: Reusability, A](deps: Ctx => D)(effect: Ctx => D => IO[A]): Pot[A]

useEffectKeepResultWhenDepsReady[D: Reusability, A](deps: => D)(effect: D => IO[A]): Pot[A]
useEffectKeepResultWhenDepsReadyBy[D: Reusability, A](deps: Ctx => D)(effect: Ctx => D => IO[A]): Pot[A]

useEffectKeepResultWhenDepsReadyOrChange[D: Reusability, A](deps: => D)(effect: D => IO[A]): Pot[A]
useEffectKeepResultWhenDepsReadyOrChangeBy[D: Reusability, A](deps: Ctx => D)(effect: Ctx => D => IO[A]): Pot[A]
```

#### Example:
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Global / onChangedBuildSource := ReloadOnSourceChanges

ThisBuild / crossScalaVersions := List("3.5.0")
ThisBuild / tlBaseVersion := "0.44"
ThisBuild / tlBaseVersion := "0.45"

ThisBuild / tlCiReleaseBranches := Seq("master")

Expand Down
127 changes: 100 additions & 27 deletions modules/core/js/src/main/scala/crystal/react/hooks/UseEffectResult.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import japgolly.scalajs.react.hooks.CustomHook
import japgolly.scalajs.react.util.DefaultEffects.Async as DefaultA

object UseEffectResult {
private case class Input[D, A](effect: WithPotDeps[D, DefaultA[A]], keep: Boolean):
private case class Input[D, A, R: Reusability](
effect: WithPotDeps[D, DefaultA[A], R],
keep: Boolean
):
val depsOpt: Option[D] = effect.deps.toOption

private def hook[D: Reusability, A] =
CustomHook[Input[D, A]]
private def hook[D, A, R: Reusability] =
CustomHook[Input[D, A, R]]
.useState(Pot.pending[A])
.useMemoBy((props, _) => props.depsOpt.void): (props, _) => // Memo Option[effect]
.useMemoBy((props, _) => props.effect.reuseValue): (props, _) => // Memo Option[effect]
_ => props.depsOpt.map(props.effect.fromDeps)
.useEffectWithDepsBy((_, _, effectOpt) => effectOpt): (props, state, _) => // Set to Pending
_ => state.setState(Pot.pending).unless(props.keep).void
Expand Down Expand Up @@ -96,14 +99,24 @@ object UseEffectResult {
): step.Next[Pot[A]] =
useEffectResultInternalWhenDepsReadyBy(deps.andThen(_.ready))(effect, keep)

private def useEffectResultInternalWhenDepsReadyBy[D: Reusability, A](
private def useEffectResultInternalWhenDepsReadyBy[D, A](
deps: Ctx => Pot[D]
)(effect: Ctx => D => DefaultA[A], keep: Boolean)(using
step: Step
): step.Next[Pot[A]] =
api.customBy { ctx =>
val hookInstance = hook[D, A]
hookInstance(Input(WithPotDeps(deps(ctx), effect(ctx)), keep))
val hookInstance = hook[D, A, Unit]
hookInstance(Input(WithPotDeps.WhenReady(deps(ctx), effect(ctx)), keep))
}

private def useEffectResultInternalWhenDepsReadyOrChangeBy[D: Reusability, A](
deps: Ctx => Pot[D]
)(effect: Ctx => D => DefaultA[A], keep: Boolean)(using
step: Step
): step.Next[Pot[A]] =
api.customBy { ctx =>
val hookInstance = hook[D, A, D]
hookInstance(Input(WithPotDeps.WhenReadyOrChange(deps(ctx), effect(ctx)), keep))
}

/**
Expand All @@ -118,18 +131,33 @@ object UseEffectResult {
useEffectResultInternalWithDepsBy(deps)(effect, keep = false)

/**
* Runs an async effect when `Pot` dependencies transition into a `Ready` state and stores the
* result in a state, which is provided as a `Pot[A]`. When dependencies change, reverts to
* `Pending` while executing the new effect or while waiting for them to become `Ready` again.
* For multiple dependencies, use `(par1, par2, ...).tupled`.
* Runs an async effect whenever `Pot` dependencies transition into a `Ready` state (but not
* when they change once `Ready`) and stores the result in a state, which is provided as a
* `Pot[A]`. When dependencies change, reverts to `Pending` while executing the new effect or
* while waiting for them to become `Ready` again. For multiple dependencies, use `(par1,
* par2, ...).tupled`.
*/
final def useEffectResultWhenDepsReadyBy[D: Reusability, A](
final def useEffectResultWhenDepsReadyBy[D, A](
deps: Ctx => Pot[D]
)(effect: Ctx => D => DefaultA[A])(using
step: Step
): step.Next[Pot[A]] =
useEffectResultInternalWhenDepsReadyBy(deps)(effect, keep = false)

/**
* Runs an async effect when `Pot` dependencies transition into a `Ready` state or change once
* `Ready` and stores the result in a state, which is provided as a `Pot[A]`. When
* dependencies change, reverts to `Pending` while executing the new effect or while waiting
* for them to become `Ready` again. For multiple dependencies, use `(par1, par2,
* ...).tupled`.
*/
final def useEffectResultWhenDepsReadyOrChangeBy[D: Reusability, A](
deps: Ctx => Pot[D]
)(effect: Ctx => D => DefaultA[A])(using
step: Step
): step.Next[Pot[A]] =
useEffectResultInternalWhenDepsReadyOrChangeBy(deps)(effect, keep = false)

/**
* Runs an async effect and stores the result in a state, which is provided as a `Pot[A]`.
* When dependencies change, keeps the old value while executing the new effect.
Expand All @@ -142,18 +170,33 @@ object UseEffectResult {
useEffectResultInternalWithDepsBy(deps)(effect, keep = true)

/**
* Runs an async effect when `Pot` dependencies transition into a `Ready` state and stores the
* result in a state, which is provided as a `Pot[A]`. When dependencies change, keeps the old
* value while executing the new effect or while waiting for them to become `Ready` again. For
* multiple dependencies, use `(par1, par2, ...).tupled`.
* Runs an async effect whenever `Pot` dependencies transition into a `Ready` state (but not
* when they change once `Ready`) and stores the result in a state, which is provided as a
* `Pot[A]`. When dependencies change, keeps the old value while executing the new effect or
* while waiting for them to become `Ready` again. For multiple dependencies, use `(par1,
* par2, ...).tupled`.
*/
final def useEffectKeepResultWhenDepsReadyBy[D: Reusability, A](
final def useEffectKeepResultWhenDepsReadyBy[D, A](
deps: Ctx => Pot[D]
)(effect: Ctx => D => DefaultA[A])(using
step: Step
): step.Next[Pot[A]] =
useEffectResultInternalWhenDepsReadyBy(deps)(effect, keep = true)

/**
* Runs an async effect whenever `Pot` dependencies transition into a `Ready` state or change
* once `Ready` and stores the result in a state, which is provided as a `Pot[A]`. When
* dependencies change, keeps the old value while executing the new effect or while waiting
* for them to become `Ready` again. For multiple dependencies, use `(par1, par2,
* ...).tupled`.
*/
final def useEffectKeepResultWhenDepsReadyOrChangeBy[D: Reusability, A](
deps: Ctx => Pot[D]
)(effect: Ctx => D => DefaultA[A])(using
step: Step
): step.Next[Pot[A]] =
useEffectResultInternalWhenDepsReadyOrChangeBy(deps)(effect, keep = true)

/**
* Runs an async effect and stores the result in a state, which is provided as a `Pot[A]`.
*/
Expand All @@ -179,18 +222,33 @@ object UseEffectResult {
useEffectResultWithDepsBy(step.squash(deps)(_))(step.squash(effect)(_))

/**
* Runs an async effect when `Pot` dependencies transition into a `Ready` state and stores the
* result in a state, which is provided as a `Pot[A]`. When dependencies change, reverts to
* `Pending` while executing the new effect or while waiting for them to become `Ready` again.
* For multiple dependencies, use `(par1, par2, ...).tupled`.
* Runs an async effect whenever `Pot` dependencies transition into a `Ready` state (but not
* when they change once `Ready`) and stores the result in a state, which is provided as a
* `Pot[A]`. When dependencies change, reverts to `Pending` while executing the new effect or
* while waiting for them to become `Ready` again. For multiple dependencies, use `(par1,
* par2, ...).tupled`.
*/
def useEffectResultWhenDepsReadyBy[D: Reusability, A](
def useEffectResultWhenDepsReadyBy[D, A](
deps: CtxFn[Pot[D]]
)(effect: CtxFn[D => DefaultA[A]])(using
step: Step
): step.Next[Pot[A]] =
useEffectResultWhenDepsReadyBy(step.squash(deps)(_))(step.squash(effect)(_))

/**
* Runs an async effect when `Pot` dependencies transition into a `Ready` state or change once
* `Ready` and stores the result in a state, which is provided as a `Pot[A]`. When
* dependencies change, reverts to `Pending` while executing the new effect or while waiting
* for them to become `Ready` again. For multiple dependencies, use `(par1, par2,
* ...).tupled`.
*/
def useEffectResultWhenDepsReadyOrChangeBy[D: Reusability, A](
deps: CtxFn[Pot[D]]
)(effect: CtxFn[D => DefaultA[A]])(using
step: Step
): step.Next[Pot[A]] =
useEffectResultWhenDepsReadyOrChangeBy(step.squash(deps)(_))(step.squash(effect)(_))

/**
* Runs an async effect and stores the result in a state, which is provided as a `Pot[A]`.
* When dependencies change, keeps the old value while executing the new effect.
Expand All @@ -203,18 +261,33 @@ object UseEffectResult {
useEffectKeepResultWithDepsBy(step.squash(deps)(_))(step.squash(effect)(_))

/**
* Runs an async effect when `Pot` dependencies transition into a `Ready` state and stores the
* result in a state, which is provided as a `Pot[A]`. When dependencies change, keeps the old
* value while executing the new effect or while waiting for them to become `Ready` again. For
* multiple dependencies, use `(par1, par2, ...).tupled`.
* Runs an async effect whenever `Pot` dependencies transition into a `Ready` state (but not
* when they change once `Ready`) and stores the result in a state, which is provided as a
* `Pot[A]`. When dependencies change, keeps the old value while executing the new effect or
* while waiting for them to become `Ready` again. For multiple dependencies, use `(par1,
* par2, ...).tupled`.
*/
def useEffectKeepResultWhenDepsReadysBy[D: Reusability, A](
def useEffectKeepResultWhenDepsReadyBy[D, A](
deps: CtxFn[Pot[D]]
)(effect: CtxFn[D => DefaultA[A]])(using
step: Step
): step.Next[Pot[A]] =
useEffectKeepResultWhenDepsReadyBy(step.squash(deps)(_))(step.squash(effect)(_))

/**
* Runs an async effect when `Pot` dependencies transition into a `Ready` state or change once
* `Ready` and stores the result in a state, which is provided as a `Pot[A]`. When
* dependencies change, keeps the old value while executing the new effect or while waiting
* for them to become `Ready` again. For multiple dependencies, use `(par1, par2,
* ...).tupled`.
*/
def useEffectKeepResultWhenDepsReadyOrChangeBy[D: Reusability, A](
deps: CtxFn[Pot[D]]
)(effect: CtxFn[D => DefaultA[A]])(using
step: Step
): step.Next[Pot[A]] =
useEffectKeepResultWhenDepsReadyOrChangeBy(step.squash(deps)(_))(step.squash(effect)(_))

/**
* Runs an async effect and stores the result in a state, which is provided as a `Pot[A]`.
*/
Expand Down
Loading
Loading