This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/functional/hooks/
use_reducer.rs

1use std::cell::RefCell;
2use std::fmt;
3use std::marker::PhantomData;
4use std::ops::Deref;
5use std::rc::Rc;
6
7use implicit_clone::ImplicitClone;
8
9use crate::functional::{hook, Hook, HookContext};
10use crate::html::IntoPropValue;
11use crate::Callback;
12
13type DispatchFn<T> = Rc<dyn Fn(<T as Reducible>::Action)>;
14
15/// A trait that implements a reducer function of a type.
16pub trait Reducible {
17    /// The action type of the reducer.
18    type Action;
19
20    /// The reducer function.
21    fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self>;
22}
23
24struct UseReducer<T>
25where
26    T: Reducible,
27{
28    current_state: Rc<RefCell<Rc<T>>>,
29
30    dispatch: DispatchFn<T>,
31}
32
33/// State handle for [`use_reducer`] and [`use_reducer_eq`] hook
34pub struct UseReducerHandle<T>
35where
36    T: Reducible,
37{
38    value: Rc<T>,
39    dispatch: DispatchFn<T>,
40}
41
42impl<T> UseReducerHandle<T>
43where
44    T: Reducible,
45{
46    /// Dispatch the given action to the reducer.
47    pub fn dispatch(&self, value: T::Action) {
48        (self.dispatch)(value)
49    }
50
51    /// Returns the dispatcher of the current state.
52    pub fn dispatcher(&self) -> UseReducerDispatcher<T> {
53        UseReducerDispatcher {
54            dispatch: self.dispatch.clone(),
55        }
56    }
57}
58
59impl<T> Deref for UseReducerHandle<T>
60where
61    T: Reducible,
62{
63    type Target = T;
64
65    fn deref(&self) -> &Self::Target {
66        &self.value
67    }
68}
69
70impl<T> Clone for UseReducerHandle<T>
71where
72    T: Reducible,
73{
74    fn clone(&self) -> Self {
75        Self {
76            value: Rc::clone(&self.value),
77            dispatch: Rc::clone(&self.dispatch),
78        }
79    }
80}
81
82impl<T> fmt::Debug for UseReducerHandle<T>
83where
84    T: Reducible + fmt::Debug,
85{
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        f.debug_struct("UseReducerHandle")
88            .field("value", &format!("{:?}", self.value))
89            .finish()
90    }
91}
92
93impl<T> PartialEq for UseReducerHandle<T>
94where
95    T: Reducible + PartialEq,
96{
97    fn eq(&self, rhs: &Self) -> bool {
98        self.value == rhs.value
99    }
100}
101
102impl<T> ImplicitClone for UseReducerHandle<T> where T: Reducible {}
103
104/// Dispatcher handle for [`use_reducer`] and [`use_reducer_eq`] hook
105pub struct UseReducerDispatcher<T>
106where
107    T: Reducible,
108{
109    dispatch: DispatchFn<T>,
110}
111
112impl<T> Clone for UseReducerDispatcher<T>
113where
114    T: Reducible,
115{
116    fn clone(&self) -> Self {
117        Self {
118            dispatch: Rc::clone(&self.dispatch),
119        }
120    }
121}
122
123impl<T> fmt::Debug for UseReducerDispatcher<T>
124where
125    T: Reducible + fmt::Debug,
126{
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.debug_struct("UseReducerDispatcher").finish()
129    }
130}
131
132impl<T> PartialEq for UseReducerDispatcher<T>
133where
134    T: Reducible,
135{
136    fn eq(&self, rhs: &Self) -> bool {
137        // We are okay with comparisons from different compilation units to result in false
138        // not-equal results. This should only lead in the worst-case to some unneeded
139        // re-renders.
140        #[allow(ambiguous_wide_pointer_comparisons)]
141        Rc::ptr_eq(&self.dispatch, &rhs.dispatch)
142    }
143}
144
145impl<T> ImplicitClone for UseReducerDispatcher<T> where T: Reducible {}
146
147impl<T> From<UseReducerDispatcher<T>> for Callback<<T as Reducible>::Action>
148where
149    T: Reducible,
150{
151    fn from(val: UseReducerDispatcher<T>) -> Self {
152        Callback { cb: val.dispatch }
153    }
154}
155
156impl<T> IntoPropValue<Callback<<T as Reducible>::Action>> for UseReducerDispatcher<T>
157where
158    T: Reducible,
159{
160    fn into_prop_value(self) -> Callback<<T as Reducible>::Action> {
161        Callback { cb: self.dispatch }
162    }
163}
164
165impl<T> UseReducerDispatcher<T>
166where
167    T: Reducible,
168{
169    /// Dispatch the given action to the reducer.
170    pub fn dispatch(&self, value: T::Action) {
171        (self.dispatch)(value)
172    }
173
174    /// Get a callback, invoking which is equivalent to calling `dispatch()`
175    /// on this same dispatcher.
176    pub fn to_callback(&self) -> Callback<<T as Reducible>::Action> {
177        Callback {
178            cb: self.dispatch.clone(),
179        }
180    }
181}
182
183/// The base function of [`use_reducer`] and [`use_reducer_eq`]
184fn use_reducer_base<'hook, T>(
185    init_fn: impl 'hook + FnOnce() -> T,
186    should_render_fn: fn(&T, &T) -> bool,
187) -> impl 'hook + Hook<Output = UseReducerHandle<T>>
188where
189    T: Reducible + 'static,
190{
191    struct HookProvider<'hook, T, F>
192    where
193        T: Reducible + 'static,
194        F: 'hook + FnOnce() -> T,
195    {
196        _marker: PhantomData<&'hook ()>,
197
198        init_fn: F,
199        should_render_fn: fn(&T, &T) -> bool,
200    }
201
202    impl<'hook, T, F> Hook for HookProvider<'hook, T, F>
203    where
204        T: Reducible + 'static,
205        F: 'hook + FnOnce() -> T,
206    {
207        type Output = UseReducerHandle<T>;
208
209        fn run(self, ctx: &mut HookContext) -> Self::Output {
210            let Self {
211                init_fn,
212                should_render_fn,
213                ..
214            } = self;
215
216            let state = ctx.next_state(move |re_render| {
217                let val = Rc::new(RefCell::new(Rc::new(init_fn())));
218                let should_render_fn = Rc::new(should_render_fn);
219
220                UseReducer {
221                    current_state: val.clone(),
222                    dispatch: Rc::new(move |action: T::Action| {
223                        let should_render = {
224                            let should_render_fn = should_render_fn.clone();
225                            let mut val = val.borrow_mut();
226                            let next_val = (*val).clone().reduce(action);
227                            let should_render = should_render_fn(&next_val, &val);
228                            *val = next_val;
229
230                            should_render
231                        };
232
233                        // Currently, this triggers a render immediately, so we need to release the
234                        // borrowed reference first.
235                        if should_render {
236                            re_render()
237                        }
238                    }),
239                }
240            });
241
242            let value = state.current_state.borrow().clone();
243            let dispatch = state.dispatch.clone();
244
245            UseReducerHandle { value, dispatch }
246        }
247    }
248
249    HookProvider {
250        _marker: PhantomData,
251        init_fn,
252        should_render_fn,
253    }
254}
255
256/// This hook is an alternative to [`use_state`](super::use_state()).
257/// It is used to handle component's state and is used when complex actions needs to be performed on
258/// said state.
259///
260/// The state is expected to implement the [`Reducible`] trait which provides an `Action` type and a
261/// reducer function.
262///
263/// The state object returned by the initial state function is required to
264/// implement a `Reducible` trait which defines the associated `Action` type and a
265/// reducer function.
266///
267/// This hook will always trigger a re-render upon receiving an action. See
268/// [`use_reducer_eq`] if you want the component to only re-render when the state changes.
269///
270/// # Example
271/// ```rust
272/// # use yew::prelude::*;
273/// # use std::rc::Rc;
274/// #
275///
276/// /// reducer's Action
277/// enum CounterAction {
278///     Double,
279///     Square,
280/// }
281///
282/// /// reducer's State
283/// struct CounterState {
284///     counter: i32,
285/// }
286///
287/// impl Default for CounterState {
288///     fn default() -> Self {
289///         Self { counter: 1 }
290///     }
291/// }
292///
293/// impl Reducible for CounterState {
294///     /// Reducer Action Type
295///     type Action = CounterAction;
296///
297///     /// Reducer Function
298///     fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
299///         let next_ctr = match action {
300///             CounterAction::Double => self.counter * 2,
301///             CounterAction::Square => self.counter.pow(2),
302///         };
303///
304///         Self { counter: next_ctr }.into()
305///     }
306/// }
307///
308/// #[component(UseReducer)]
309/// fn reducer() -> Html {
310///     // The use_reducer hook takes an initialization function which will be called only once.
311///     let counter = use_reducer(CounterState::default);
312///
313///     let double_onclick = {
314///         let counter = counter.clone();
315///         Callback::from(move |_| counter.dispatch(CounterAction::Double))
316///     };
317///     let square_onclick = {
318///         let counter = counter.clone();
319///         Callback::from(move |_| counter.dispatch(CounterAction::Square))
320///     };
321///
322///     html! {
323///         <>
324///             <div id="result">{ counter.counter }</div>
325///
326///             <button onclick={double_onclick}>{ "Double" }</button>
327///             <button onclick={square_onclick}>{ "Square" }</button>
328///         </>
329///     }
330/// }
331/// ```
332///
333/// # Tip
334///
335/// The dispatch function is guaranteed to be the same across the entire
336/// component lifecycle. You can safely omit the `UseReducerHandle` from the
337/// dependents of `use_effect_with` if you only intend to dispatch
338/// values from within the hooks.
339///
340/// # Caution
341///
342/// The value held in the handle will reflect the value of at the time the
343/// handle is returned by the `use_reducer`. It is possible that the handle does
344/// not dereference to an up to date value if you are moving it into a
345/// `use_effect_with` hook. You can register the
346/// state to the dependents so the hook can be updated when the value changes.
347#[hook]
348pub fn use_reducer<T, F>(init_fn: F) -> UseReducerHandle<T>
349where
350    T: Reducible + 'static,
351    F: FnOnce() -> T,
352{
353    use_reducer_base(init_fn, |_, _| true)
354}
355
356/// [`use_reducer`] but only re-renders when `prev_state != next_state`.
357///
358/// This requires the state to implement [`PartialEq`] in addition to the [`Reducible`] trait
359/// required by [`use_reducer`].
360#[hook]
361pub fn use_reducer_eq<T, F>(init_fn: F) -> UseReducerHandle<T>
362where
363    T: Reducible + PartialEq + 'static,
364    F: FnOnce() -> T,
365{
366    use_reducer_base(init_fn, T::ne)
367}