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

useEffectKeepResult #634

Merged
merged 2 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
Loading