Skip to content

Commit

Permalink
useEffectKeepResult
Browse files Browse the repository at this point in the history
  • Loading branch information
rpiaggio committed Jul 24, 2024
1 parent 565868b commit 8d55f2c
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 14 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,16 @@ Stores the result `A` of an effect in state. The state is provided as `Pot[A]`,

Note that all versions either have dependencies or are executed `onMount`. It doesn't make sense to execute the effect on each render since its completion will alter state and force a rerender, which would provoke a render loop. The naming keeps the `WithDeps`, even though it's redundant, for consistency with the `useEffect` family of hooks.

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.


``` scala
useEffectResultWithDeps[D: Reusability, A](deps: => D)(effect: D => IO[A]): Pot[A]
useEffectResultWithDepsBy[D: Reusability, A](deps: Ctx => D)(effect: Ctx => D => IO[A]): Pot[A]

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

useEffectResultOnMount[A](effect: IO[A]): Pot[A]
useEffectResultOnMountBy[A](effect: Ctx => IO[A]): Pot[A]
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,29 @@ import japgolly.scalajs.react.hooks.CustomHook
import japgolly.scalajs.react.util.DefaultEffects.Async as DefaultA

object UseEffectResult {
def hook[D: Reusability, A] = CustomHook[WithDeps[D, DefaultA[A]]]
.useState(Pot.pending[A])
.useEffectWithDepsBy((props, _) => props.deps)((_, state) => _ => state.setState(Pot.pending))
.useAsyncEffectWithDepsBy((props, _) => props.deps): (props, state) =>
deps =>
(for {
a <- props.fromDeps(deps)
_ <- state.setStateAsync(a.ready)
} yield ()).handleErrorWith(t => state.setStateAsync(Pot.error(t)))
.buildReturning((_, state) => state.value)
private case class Input[A](effect: DefaultA[A], keep: Boolean)

private def hook[D: Reusability, A] =
CustomHook[WithDeps[D, Input[A]]]
.useState(Pot.pending[A])
.useMemoBy((props, _) => props.deps): (props, _) =>
deps => props.fromDeps(deps)
.useEffectWithDepsBy((_, _, input) => input): (_, state, _) =>
input => state.setState(Pot.pending).unless(input.keep).void
.useAsyncEffectWithDepsBy((_, _, input) => input): (_, state, _) =>
input =>
(for {
a <- input.effect
_ <- state.setStateAsync(a.ready)
} yield ()).handleErrorWith(t => state.setStateAsync(Pot.error(t)))
.buildReturning((_, state, _) => state.value)

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

/**
* Runs an async effect 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.
*/
final def useEffectResultWithDeps[D: Reusability, A](
deps: => D
Expand All @@ -34,6 +41,17 @@ object UseEffectResult {
): step.Next[Pot[A]] =
useEffectResultWithDepsBy(_ => deps)(_ => 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.
*/
final def useEffectKeepResultWithDeps[D: Reusability, A](
deps: => D
)(effect: D => DefaultA[A])(using
step: Step
): step.Next[Pot[A]] =
useEffectKeepResultWithDepsBy(_ => deps)(_ => effect)

/**
* Runs an async effect and stores the result in a state, which is provided as a `Pot[A]`.
*/
Expand All @@ -42,18 +60,37 @@ object UseEffectResult {
): step.Next[Pot[A]] =
useEffectResultOnMountBy(_ => effect)

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

/**
* Runs an async effect 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.
*/
final def useEffectResultWithDepsBy[D: Reusability, A](
deps: Ctx => D
)(effect: Ctx => D => DefaultA[A])(using
step: Step
): step.Next[Pot[A]] =
api.customBy { ctx =>
val hookInstance = hook[D, A]
hookInstance(WithDeps(deps(ctx), effect(ctx)))
}
useEffectResultInternalWithDepsBy(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.
*/
final def useEffectKeepResultWithDepsBy[D: Reusability, A](
deps: Ctx => D
)(effect: Ctx => D => DefaultA[A])(using
step: Step
): step.Next[Pot[A]] =
useEffectResultInternalWithDepsBy(deps)(effect, keep = true)

/**
* Runs an async effect and stores the result in a state, which is provided as a `Pot[A]`.
Expand All @@ -70,6 +107,7 @@ object UseEffectResult {

/**
* Runs an async effect 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.
*/
def useEffectResultWithDepsBy[D: Reusability, A](
deps: CtxFn[D]
Expand All @@ -78,6 +116,17 @@ object UseEffectResult {
): step.Next[Pot[A]] =
useEffectResultWithDepsBy(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.
*/
def useEffectKeepResultWithDepsBy[D: Reusability, A](
deps: CtxFn[D]
)(effect: CtxFn[D => DefaultA[A]])(using
step: Step
): step.Next[Pot[A]] =
useEffectKeepResultWithDepsBy(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

0 comments on commit 8d55f2c

Please sign in to comment.