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