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