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

yew/functional/
mod.rs

1//! Function components are a simplified version of normal components.
2//! They consist of a single function annotated with the attribute `#[component]`
3//! that receives props and determines what should be rendered by returning [`Html`](crate::Html).
4//!
5//! Functions with the attribute have to return `Html` and may take a single parameter for the type
6//! of props the component should accept. The parameter type needs to be a reference to a
7//! `Properties` type (ex. `props: &MyProps`). If the function doesn't have any parameters the
8//! resulting component doesn't accept any props.
9//!
10//! Just mark the component with the attribute. The component will be named after the function.
11//!
12//! ```rust
13//! # use yew::prelude::*;
14//! #
15//! #[component]
16//! fn HelloWorld() -> Html {
17//!     html! { "Hello world" }
18//! }
19//! ```
20//!
21//! More details about function components and Hooks can be found on [Yew Docs](https://yew.rs/docs/next/concepts/function-components/introduction)
22
23use std::any::Any;
24use std::cell::RefCell;
25use std::fmt;
26use std::rc::Rc;
27
28use wasm_bindgen::prelude::*;
29
30#[cfg(all(feature = "hydration", feature = "ssr"))]
31use crate::html::RenderMode;
32use crate::html::{AnyScope, BaseComponent, Context, HtmlResult};
33use crate::Properties;
34
35mod hooks;
36pub use hooks::*;
37/// This attribute creates a function component from a normal Rust function.
38///
39/// Functions with this attribute **must** return `Html` and can optionally take an argument
40/// for props. Note that the function only receives a reference to the props.
41///
42/// When using this attribute you need to provide a name for the component:
43/// `#component(ComponentName)]`.
44/// The attribute will then automatically create a [`FunctionComponent`] with the given
45/// identifier which you can use like a normal component.
46///
47/// # Example
48/// ```rust
49/// # use yew::prelude::*;
50/// #
51/// # #[derive(Properties, Clone, PartialEq)]
52/// # pub struct Props {
53/// #     text: String
54/// # }
55/// #
56/// #[component(NameOfComponent)]
57/// pub fn component(props: &Props) -> Html {
58///     html! {
59///         <p>{ &props.text }</p>
60///     }
61/// }
62/// ```
63pub use yew_macro::function_component as component;
64/// A re-export of [`component`](yew_macro::function_component) with the older name.
65#[deprecated(since = "0.22.0", note = "renamed to `#[component]")]
66pub use yew_macro::function_component;
67/// This attribute creates a user-defined hook from a normal Rust function.
68pub use yew_macro::hook;
69
70type ReRender = Rc<dyn Fn()>;
71
72/// Primitives of a prepared state hook.
73#[cfg(any(feature = "hydration", feature = "ssr"))]
74pub(crate) trait PreparedState {
75    #[cfg(feature = "ssr")]
76    fn prepare(&self) -> String;
77}
78
79/// Primitives of an effect hook.
80pub(crate) trait Effect {
81    fn rendered(&self) {}
82}
83
84/// A hook context to be passed to hooks.
85pub struct HookContext {
86    pub(crate) scope: AnyScope,
87    #[cfg(all(feature = "hydration", feature = "ssr"))]
88    creation_mode: RenderMode,
89    re_render: ReRender,
90
91    states: Vec<Rc<dyn Any>>,
92    effects: Vec<Rc<dyn Effect>>,
93
94    #[cfg(any(feature = "hydration", feature = "ssr"))]
95    prepared_states: Vec<Rc<dyn PreparedState>>,
96
97    #[cfg(feature = "hydration")]
98    prepared_states_data: Vec<Rc<str>>,
99    #[cfg(feature = "hydration")]
100    prepared_state_counter: usize,
101
102    counter: usize,
103    #[cfg(debug_assertions)]
104    total_hook_counter: Option<usize>,
105}
106
107impl HookContext {
108    fn new(
109        scope: AnyScope,
110        re_render: ReRender,
111        #[cfg(all(feature = "hydration", feature = "ssr"))] creation_mode: RenderMode,
112        #[cfg(feature = "hydration")] prepared_state: Option<&str>,
113    ) -> RefCell<Self> {
114        RefCell::new(HookContext {
115            scope,
116            re_render,
117
118            #[cfg(all(feature = "hydration", feature = "ssr"))]
119            creation_mode,
120
121            states: Vec::new(),
122
123            #[cfg(any(feature = "hydration", feature = "ssr"))]
124            prepared_states: Vec::new(),
125            effects: Vec::new(),
126
127            #[cfg(feature = "hydration")]
128            prepared_states_data: {
129                match prepared_state {
130                    Some(m) => m.split(',').map(Rc::from).collect(),
131                    None => Vec::new(),
132                }
133            },
134            #[cfg(feature = "hydration")]
135            prepared_state_counter: 0,
136
137            counter: 0,
138            #[cfg(debug_assertions)]
139            total_hook_counter: None,
140        })
141    }
142
143    pub(crate) fn next_state<T>(&mut self, initializer: impl FnOnce(ReRender) -> T) -> Rc<T>
144    where
145        T: 'static,
146    {
147        // Determine which hook position we're at and increment for the next hook
148        let hook_pos = self.counter;
149        self.counter += 1;
150
151        let state = match self.states.get(hook_pos).cloned() {
152            Some(m) => m,
153            None => {
154                let initial_state = Rc::new(initializer(self.re_render.clone()));
155                self.states.push(initial_state.clone());
156
157                initial_state
158            }
159        };
160
161        state.downcast().unwrap_throw()
162    }
163
164    pub(crate) fn next_effect<T>(&mut self, initializer: impl FnOnce(ReRender) -> T) -> Rc<T>
165    where
166        T: 'static + Effect,
167    {
168        let prev_state_len = self.states.len();
169        let t = self.next_state(initializer);
170
171        // This is a new effect, we add it to effects.
172        if self.states.len() != prev_state_len {
173            self.effects.push(t.clone());
174        }
175
176        t
177    }
178
179    #[cfg(any(feature = "hydration", feature = "ssr"))]
180    pub(crate) fn next_prepared_state<T>(
181        &mut self,
182        initializer: impl FnOnce(ReRender, Option<&str>) -> T,
183    ) -> Rc<T>
184    where
185        T: 'static + PreparedState,
186    {
187        #[cfg(not(feature = "hydration"))]
188        let prepared_state = Option::<Rc<str>>::None;
189
190        #[cfg(feature = "hydration")]
191        let prepared_state = {
192            let prepared_state_pos = self.prepared_state_counter;
193            self.prepared_state_counter += 1;
194
195            self.prepared_states_data.get(prepared_state_pos).cloned()
196        };
197
198        let prev_state_len = self.states.len();
199        let t = self.next_state(move |re_render| initializer(re_render, prepared_state.as_deref()));
200
201        // This is a new effect, we add it to effects.
202        if self.states.len() != prev_state_len {
203            self.prepared_states.push(t.clone());
204        }
205
206        t
207    }
208
209    #[inline(always)]
210    fn prepare_run(&mut self) {
211        #[cfg(feature = "hydration")]
212        {
213            self.prepared_state_counter = 0;
214        }
215
216        self.counter = 0;
217    }
218
219    /// asserts hook counter.
220    ///
221    /// This function asserts that the number of hooks matches for every render.
222    #[cfg(debug_assertions)]
223    fn assert_hook_context(&mut self, render_ok: bool) {
224        // Procedural Macros can catch most conditionally called hooks at compile time, but it
225        // cannot detect early return (as the return can be Err(_), Suspension).
226        match (render_ok, self.total_hook_counter) {
227            // First rendered,
228            // we store the hook counter.
229            (true, None) => {
230                self.total_hook_counter = Some(self.counter);
231            }
232            // Component is suspended before it's first rendered.
233            // We don't have a total count to compare with.
234            (false, None) => {}
235
236            // Subsequent render,
237            // we compare stored total count and current render count.
238            (true, Some(total_hook_counter)) => assert_eq!(
239                total_hook_counter, self.counter,
240                "Hooks are called conditionally."
241            ),
242
243            // Subsequent suspension,
244            // components can have less hooks called when suspended, but not more.
245            (false, Some(total_hook_counter)) => assert!(
246                self.counter <= total_hook_counter,
247                "Hooks are called conditionally."
248            ),
249        }
250    }
251
252    fn run_effects(&self) {
253        for effect in self.effects.iter() {
254            effect.rendered();
255        }
256    }
257
258    fn drain_states(&mut self) {
259        // We clear the effects as these are also references to states.
260        self.effects.clear();
261
262        for state in self.states.drain(..) {
263            drop(state);
264        }
265    }
266
267    #[cfg(not(feature = "ssr"))]
268    fn prepare_state(&self) -> Option<String> {
269        None
270    }
271
272    #[cfg(feature = "ssr")]
273    fn prepare_state(&self) -> Option<String> {
274        if self.prepared_states.is_empty() {
275            return None;
276        }
277
278        let prepared_states = self.prepared_states.clone();
279
280        let mut states = Vec::new();
281
282        for state in prepared_states.iter() {
283            let state = state.prepare();
284            states.push(state);
285        }
286
287        Some(states.join(","))
288    }
289}
290
291impl fmt::Debug for HookContext {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        f.write_str("HookContext<_>")
294    }
295}
296
297/// Trait that allows a struct to act as Function Component.
298pub trait FunctionProvider {
299    /// Properties for the Function Component.
300    type Properties: Properties + PartialEq;
301
302    /// Render the component. This function returns the [`Html`](crate::Html) to be rendered for the
303    /// component.
304    ///
305    /// Equivalent of [`Component::view`](crate::html::Component::view).
306    fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult;
307}
308
309/// A type that interacts [`FunctionProvider`] to provide lifecycle events to be bridged to
310/// [`BaseComponent`].
311///
312/// # Note
313///
314/// Function Components should not be implemented with this type directly.
315///
316/// Use the `#[component]` macro instead.
317#[doc(hidden)]
318pub struct FunctionComponent<T>
319where
320    T: FunctionProvider,
321{
322    _never: std::marker::PhantomData<T>,
323    hook_ctx: RefCell<HookContext>,
324}
325
326impl<T> FunctionComponent<T>
327where
328    T: FunctionProvider + 'static,
329{
330    /// Creates a new function component.
331    pub fn new(ctx: &Context<T>) -> Self
332    where
333        T: BaseComponent<Message = ()> + FunctionProvider + 'static,
334    {
335        let scope = AnyScope::from(ctx.link().clone());
336        let re_render = {
337            let link = ctx.link().clone();
338
339            Rc::new(move || link.send_message(()))
340        };
341
342        Self {
343            _never: std::marker::PhantomData,
344            hook_ctx: HookContext::new(
345                scope,
346                re_render,
347                #[cfg(all(feature = "hydration", feature = "ssr"))]
348                ctx.creation_mode(),
349                #[cfg(feature = "hydration")]
350                ctx.prepared_state(),
351            ),
352        }
353    }
354
355    /// Renders a function component.
356    pub fn render(&self, props: &T::Properties) -> HtmlResult {
357        let mut hook_ctx = self.hook_ctx.borrow_mut();
358
359        hook_ctx.prepare_run();
360
361        #[allow(clippy::let_and_return)]
362        let result = T::run(&mut hook_ctx, props);
363
364        #[cfg(debug_assertions)]
365        hook_ctx.assert_hook_context(result.is_ok());
366
367        result
368    }
369
370    /// Run Effects of a function component.
371    pub fn rendered(&self) {
372        let hook_ctx = self.hook_ctx.borrow();
373        hook_ctx.run_effects();
374    }
375
376    /// Destroys the function component.
377    pub fn destroy(&self) {
378        let mut hook_ctx = self.hook_ctx.borrow_mut();
379        hook_ctx.drain_states();
380    }
381
382    /// Prepares the server-side state.
383    pub fn prepare_state(&self) -> Option<String> {
384        let hook_ctx = self.hook_ctx.borrow();
385        hook_ctx.prepare_state()
386    }
387}
388
389impl<T> fmt::Debug for FunctionComponent<T>
390where
391    T: FunctionProvider + 'static,
392{
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        f.write_str("FunctionComponent<_>")
395    }
396}