#[cfg(feature = "use_shared_reducer")]
pub use shared::*;
#[cfg(feature = "use_gen_reducer")]
pub use gen::*;
use super::{IntoUpdateStateResult, UpdateState};
pub trait Reduce<S, A> {
fn reduce(&mut self, state: &mut S, action: A) -> bool;
}
impl<S, A, F: FnMut(&mut S, A) -> R, R: IntoUpdateStateResult> Reduce<S, A> for F {
fn reduce(&mut self, state: &mut S, action: A) -> bool {
self(state, action).into_update_state_result()
}
}
pub struct Reducer<A, R, AS: Default + Extend<A> + IntoIterator<Item = A> = Vec<A>> {
pub reduce: R,
pub actions: AS,
}
impl<S, A, R: Reduce<S, A>, AS> UpdateState<S> for Reducer<A, R, AS>
where
AS: Default + Extend<A> + IntoIterator<Item = A>,
{
fn update_state(&mut self, state: &mut S) -> bool {
std::mem::take(&mut self.actions)
.into_iter()
.fold(false, |changed, action| {
self.reduce.reduce(state, action) || changed
})
}
}
#[cfg(feature = "use_shared_reducer")]
mod shared {
use super::{
super::{
use_shared_update_state, use_shared_update_state_with, SharedUpdateState,
UseSharedUpdateState, UseSharedUpdateStateWith,
},
Reduce, Reducer,
};
pub type SharedReducer<A, R, AS> = SharedUpdateState<Reducer<A, R, AS>>;
pub type SharedReduce<A, R> = SharedReducer<A, R, Vec<A>>;
impl<A, R, AS: Default + Extend<A> + IntoIterator<Item = A>> SharedReducer<A, R, AS> {
pub fn dispatch(&self, action: A) {
self.map_mut_update_state(|reducer| reducer.actions.extend(Some(action)))
}
pub fn dispatch_actions<AA: IntoIterator<Item = A>>(&self, actions: AA) {
self.map_mut_update_state(|reducer| reducer.actions.extend(actions))
}
}
pub type UseSharedReducer<S, A, R, AS> = UseSharedUpdateState<S, Reducer<A, R, AS>>;
pub fn use_shared_reducer<
S,
A,
R: Reduce<S, A>,
AS: Default + Extend<A> + IntoIterator<Item = A>,
>(
initial_state: S,
reduce: R,
) -> UseSharedReducer<S, A, R, AS> {
use_shared_update_state(
initial_state,
Reducer {
reduce,
actions: AS::default(),
},
)
}
pub type UseSharedReducerWith<S, A, R, AS, F> =
UseSharedUpdateStateWith<S, Reducer<A, R, AS>, F>;
pub fn use_shared_reducer_with<
S,
A,
R: Reduce<S, A>,
AS: Default + Extend<A> + IntoIterator<Item = A>,
>(
f: impl FnOnce() -> (S, R),
) -> UseSharedReducerWith<S, A, R, AS, impl FnOnce() -> (S, SharedReducer<A, R, AS>)> {
use_shared_update_state_with(move || {
let (initial_state, reduce) = f();
(
initial_state,
Reducer {
reduce,
actions: AS::default(),
},
)
})
}
pub type UseSharedReduce<S, A, R> = UseSharedReducer<S, A, R, Vec<A>>;
pub fn use_shared_reduce<S, A, R: Reduce<S, A>>(
initial_state: S,
reduce: R,
) -> UseSharedReduce<S, A, R> {
use_shared_reducer(initial_state, reduce)
}
pub type UseSharedReduceWith<S, A, R, F> = UseSharedReducerWith<S, A, R, Vec<A>, F>;
pub fn use_shared_reduce_with<S, A, R: Reduce<S, A>>(
f: impl FnOnce() -> (S, R),
) -> UseSharedReduceWith<S, A, R, impl FnOnce() -> (S, SharedReduce<A, R>)> {
use_shared_reducer_with(f)
}
#[cfg(test)]
mod tests {
use futures_lite::future::block_on;
use hooks_core::{HookExt, IntoHook};
use crate::{hook_fn, Reduce, SharedReduce};
type SharedMyReduce = SharedReduce<MyAction, MyReduce>;
struct MyReduce {
step: i32,
}
enum MyAction {
Increment,
Decrement,
}
impl Reduce<i32, MyAction> for MyReduce {
fn reduce(&mut self, state: &mut i32, action: MyAction) -> bool {
if self.step == 0 {
return false;
}
match action {
MyAction::Increment => *state += self.step,
MyAction::Decrement => *state -= self.step,
}
true
}
}
hook_fn!(
fn use_my_reduce(step: i32) -> (i32, &'hook SharedMyReduce) {
let (value, reduce) = h![crate::use_shared_reduce_with(|| (0, MyReduce { step }))];
(*value, reduce)
}
);
#[test]
fn reduce() {
block_on(async {
let mut hook = use_my_reduce(2).into_hook();
let (value, _) = hook.next_value().await.unwrap();
assert_eq!(value, 0);
assert!(hook.next_value().await.is_none());
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 0);
reduce.dispatch(MyAction::Increment);
assert_eq!(hook.next_value().await.unwrap().0, 2);
}
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 2);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Decrement);
assert_eq!(hook.next_value().await.unwrap().0, 2);
}
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 2);
reduce.map_mut_update_state(|reducer| reducer.reduce.step = 1);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Decrement);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Increment);
assert_eq!(hook.next_value().await.unwrap().0, 5);
}
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 5);
reduce.map_mut_update_state(|reducer| reducer.reduce.step = 0);
reduce.dispatch(MyAction::Increment);
assert!(hook.next_value().await.is_none());
}
assert!(hook.next_value().await.is_none());
})
}
}
}
#[cfg(feature = "use_gen_reducer")]
mod gen {
use super::{
super::{
use_gen_update_state, use_gen_update_state_with, GenUpdateStateKey,
GenUpdateStateOwner, UseGenUpdateState, UseGenUpdateStateWith,
},
Reduce, Reducer,
};
pub type GenReducerOwner<A, R, AS> = GenUpdateStateOwner<Reducer<A, R, AS>>;
pub type GenReducerKey<A, R, AS> = GenUpdateStateKey<Reducer<A, R, AS>>;
pub type GenReduceOwner<A, R> = GenReducerOwner<A, R, Vec<A>>;
pub type GenReduceKey<A, R> = GenReducerKey<A, R, Vec<A>>;
impl<A, R, AS: Default + Extend<A> + IntoIterator<Item = A>> GenReducerKey<A, R, AS> {
pub fn dispatch(&self, action: A) {
self.map_mut_update_state(|reducer| reducer.actions.extend(Some(action)))
}
pub fn dispatch_actions<AA: IntoIterator<Item = A>>(&self, actions: AA) {
self.map_mut_update_state(|reducer| reducer.actions.extend(actions))
}
}
pub type UseGenReducer<S, A, R, AS> = UseGenUpdateState<S, Reducer<A, R, AS>>;
pub fn use_gen_reducer<
S,
A,
R: Reduce<S, A>,
AS: Default + Extend<A> + IntoIterator<Item = A>,
>(
initial_state: S,
reduce: R,
) -> UseGenReducer<S, A, R, AS> {
use_gen_update_state(
initial_state,
Reducer {
reduce,
actions: AS::default(),
},
)
}
pub type UseGenReducerWith<F> = UseGenUpdateStateWith<F>;
pub fn use_gen_reducer_with<
S,
A,
R: Reduce<S, A>,
AS: Default + Extend<A> + IntoIterator<Item = A>,
>(
f: impl FnOnce() -> (S, R),
) -> UseGenReducerWith<impl FnOnce() -> (S, Reducer<A, R, AS>)> {
use_gen_update_state_with(move || {
let (initial_state, reduce) = f();
(
initial_state,
Reducer {
reduce,
actions: AS::default(),
},
)
})
}
pub type UseGenReduce<S, A, R> = UseGenReducer<S, A, R, Vec<A>>;
pub fn use_gen_reduce<S, A, R: Reduce<S, A>>(
initial_state: S,
reduce: R,
) -> UseGenReduce<S, A, R> {
use_gen_reducer(initial_state, reduce)
}
pub type UseGenReduceWith<F> = UseGenReducerWith<F>;
pub fn use_gen_reduce_with<S, A, R: Reduce<S, A>>(
f: impl FnOnce() -> (S, R),
) -> UseGenReduceWith<impl FnOnce() -> (S, Reducer<A, R, Vec<A>>)> {
use_gen_reducer_with(f)
}
#[cfg(test)]
mod tests {
use futures_lite::future::block_on;
use hooks_core::{HookExt, IntoHook};
use crate::{hook_fn, utils::testing::assert_always_pending, Reduce};
use super::{use_gen_reduce_with, GenReduceKey};
type GenMyReduceKey = GenReduceKey<MyAction, MyReduce>;
struct MyReduce {
step: i32,
}
enum MyAction {
Increment,
Decrement,
}
impl Reduce<i32, MyAction> for MyReduce {
fn reduce(&mut self, state: &mut i32, action: MyAction) -> bool {
if self.step == 0 {
return false;
}
match action {
MyAction::Increment => *state += self.step,
MyAction::Decrement => *state -= self.step,
}
true
}
}
hook_fn!(
fn use_my_reduce(step: i32) -> (i32, GenMyReduceKey) {
let (value, reduce) = h![use_gen_reduce_with(|| (0, MyReduce { step }))];
(*value, reduce)
}
);
#[test]
fn reduce() {
block_on(async {
let mut hook = use_my_reduce(2).into_hook();
let (value, _) = hook.next_value().await.unwrap();
assert_eq!(value, 0);
assert_always_pending(|| hook.next_value());
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 0);
reduce.dispatch(MyAction::Increment);
assert_eq!(hook.next_value().await.unwrap().0, 2);
}
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 2);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Decrement);
assert_eq!(hook.next_value().await.unwrap().0, 2);
}
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 2);
reduce.map_mut_update_state(|reducer| reducer.reduce.step = 1);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Decrement);
reduce.dispatch(MyAction::Increment);
reduce.dispatch(MyAction::Increment);
assert_eq!(hook.next_value().await.unwrap().0, 5);
}
{
let (value, reduce) = hook.use_hook();
assert_eq!(value, 5);
reduce.map_mut_update_state(|reducer| reducer.reduce.step = 0);
reduce.dispatch(MyAction::Increment);
assert_always_pending(|| hook.next_value());
}
assert_always_pending(|| hook.next_value());
})
}
}
}