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)
2223use std::any::Any;
24use std::cell::RefCell;
25use std::fmt;
26use std::rc::Rc;
2728use wasm_bindgen::prelude::*;
2930#[cfg(all(feature = "hydration", feature = "ssr"))]
31use crate::html::RenderMode;
32use crate::html::{AnyScope, BaseComponent, Context, HtmlResult};
33use crate::Properties;
3435mod 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;
6667type ReRender = Rc<dyn Fn()>;
6869/// Primitives of a prepared state hook.
70#[cfg(any(feature = "hydration", feature = "ssr"))]
71pub(crate) trait PreparedState {
72#[cfg(feature = "ssr")]
73fn prepare(&self) -> String;
74}
7576/// Primitives of an effect hook.
77pub(crate) trait Effect {
78fn rendered(&self) {}
79}
8081/// A hook context to be passed to hooks.
82pub struct HookContext {
83pub(crate) scope: AnyScope,
84#[cfg(all(feature = "hydration", feature = "ssr"))]
85creation_mode: RenderMode,
86 re_render: ReRender,
8788 states: Vec<Rc<dyn Any>>,
89 effects: Vec<Rc<dyn Effect>>,
9091#[cfg(any(feature = "hydration", feature = "ssr"))]
92prepared_states: Vec<Rc<dyn PreparedState>>,
9394#[cfg(feature = "hydration")]
95prepared_states_data: Vec<Rc<str>>,
96#[cfg(feature = "hydration")]
97prepared_state_counter: usize,
9899 counter: usize,
100#[cfg(debug_assertions)]
101total_hook_counter: Option<usize>,
102}
103104impl HookContext {
105fn 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,
114115#[cfg(all(feature = "hydration", feature = "ssr"))]
116creation_mode,
117118 states: Vec::new(),
119120#[cfg(any(feature = "hydration", feature = "ssr"))]
121prepared_states: Vec::new(),
122 effects: Vec::new(),
123124#[cfg(feature = "hydration")]
125prepared_states_data: {
126match prepared_state {
127Some(m) => m.split(',').map(Rc::from).collect(),
128None => Vec::new(),
129 }
130 },
131#[cfg(feature = "hydration")]
132prepared_state_counter: 0,
133134 counter: 0,
135#[cfg(debug_assertions)]
136total_hook_counter: None,
137 })
138 }
139140pub(crate) fn next_state<T>(&mut self, initializer: impl FnOnce(ReRender) -> T) -> Rc<T>
141where
142T: 'static,
143 {
144// Determine which hook position we're at and increment for the next hook
145let hook_pos = self.counter;
146self.counter += 1;
147148let state = match self.states.get(hook_pos).cloned() {
149Some(m) => m,
150None => {
151let initial_state = Rc::new(initializer(self.re_render.clone()));
152self.states.push(initial_state.clone());
153154 initial_state
155 }
156 };
157158 state.downcast().unwrap_throw()
159 }
160161pub(crate) fn next_effect<T>(&mut self, initializer: impl FnOnce(ReRender) -> T) -> Rc<T>
162where
163T: 'static + Effect,
164 {
165let prev_state_len = self.states.len();
166let t = self.next_state(initializer);
167168// This is a new effect, we add it to effects.
169if self.states.len() != prev_state_len {
170self.effects.push(t.clone());
171 }
172173 t
174 }
175176#[cfg(any(feature = "hydration", feature = "ssr"))]
177pub(crate) fn next_prepared_state<T>(
178&mut self,
179 initializer: impl FnOnce(ReRender, Option<&str>) -> T,
180 ) -> Rc<T>
181where
182T: 'static + PreparedState,
183 {
184#[cfg(not(feature = "hydration"))]
185let prepared_state = Option::<Rc<str>>::None;
186187#[cfg(feature = "hydration")]
188let prepared_state = {
189let prepared_state_pos = self.prepared_state_counter;
190self.prepared_state_counter += 1;
191192self.prepared_states_data.get(prepared_state_pos).cloned()
193 };
194195let prev_state_len = self.states.len();
196let t = self.next_state(move |re_render| initializer(re_render, prepared_state.as_deref()));
197198// This is a new effect, we add it to effects.
199if self.states.len() != prev_state_len {
200self.prepared_states.push(t.clone());
201 }
202203 t
204 }
205206#[inline(always)]
207fn prepare_run(&mut self) {
208#[cfg(feature = "hydration")]
209{
210self.prepared_state_counter = 0;
211 }
212213self.counter = 0;
214 }
215216/// asserts hook counter.
217 ///
218 /// This function asserts that the number of hooks matches for every render.
219#[cfg(debug_assertions)]
220fn 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).
223match (render_ok, self.total_hook_counter) {
224// First rendered,
225 // we store the hook counter.
226(true, None) => {
227self.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) => {}
232233// 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),
239240// Subsequent suspension,
241 // components can have less hooks called when suspended, but not more.
242(false, Some(total_hook_counter)) => assert!(
243self.counter <= total_hook_counter,
244"Hooks are called conditionally."
245),
246 }
247 }
248249fn run_effects(&self) {
250for effect in self.effects.iter() {
251 effect.rendered();
252 }
253 }
254255fn drain_states(&mut self) {
256// We clear the effects as these are also references to states.
257self.effects.clear();
258259for state in self.states.drain(..) {
260 drop(state);
261 }
262 }
263264#[cfg(not(feature = "ssr"))]
265fn prepare_state(&self) -> Option<String> {
266None
267}
268269#[cfg(feature = "ssr")]
270fn prepare_state(&self) -> Option<String> {
271if self.prepared_states.is_empty() {
272return None;
273 }
274275let prepared_states = self.prepared_states.clone();
276277let mut states = Vec::new();
278279for state in prepared_states.iter() {
280let state = state.prepare();
281 states.push(state);
282 }
283284Some(states.join(","))
285 }
286}
287288impl fmt::Debug for HookContext {
289fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 f.write_str("HookContext<_>")
291 }
292}
293294/// Trait that allows a struct to act as Function Component.
295pub trait FunctionProvider {
296/// Properties for the Function Component.
297type Properties: Properties + PartialEq;
298299/// 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).
303fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult;
304}
305306/// 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
317T: FunctionProvider,
318{
319 _never: std::marker::PhantomData<T>,
320 hook_ctx: RefCell<HookContext>,
321}
322323impl<T> FunctionComponent<T>
324where
325T: FunctionProvider + 'static,
326{
327/// Creates a new function component.
328pub fn new(ctx: &Context<T>) -> Self
329where
330T: BaseComponent<Message = ()> + FunctionProvider + 'static,
331 {
332let scope = AnyScope::from(ctx.link().clone());
333let re_render = {
334let link = ctx.link().clone();
335336 Rc::new(move || link.send_message(()))
337 };
338339Self {
340 _never: std::marker::PhantomData,
341 hook_ctx: HookContext::new(
342 scope,
343 re_render,
344#[cfg(all(feature = "hydration", feature = "ssr"))]
345ctx.creation_mode(),
346#[cfg(feature = "hydration")]
347ctx.prepared_state(),
348 ),
349 }
350 }
351352/// Renders a function component.
353pub fn render(&self, props: &T::Properties) -> HtmlResult {
354let mut hook_ctx = self.hook_ctx.borrow_mut();
355356 hook_ctx.prepare_run();
357358#[allow(clippy::let_and_return)]
359let result = T::run(&mut hook_ctx, props);
360361#[cfg(debug_assertions)]
362hook_ctx.assert_hook_context(result.is_ok());
363364 result
365 }
366367/// Run Effects of a function component.
368pub fn rendered(&self) {
369let hook_ctx = self.hook_ctx.borrow();
370 hook_ctx.run_effects();
371 }
372373/// Destroys the function component.
374pub fn destroy(&self) {
375let mut hook_ctx = self.hook_ctx.borrow_mut();
376 hook_ctx.drain_states();
377 }
378379/// Prepares the server-side state.
380pub fn prepare_state(&self) -> Option<String> {
381let hook_ctx = self.hook_ctx.borrow();
382 hook_ctx.prepare_state()
383 }
384}
385386impl<T> fmt::Debug for FunctionComponent<T>
387where
388T: FunctionProvider + 'static,
389{
390fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 f.write_str("FunctionComponent<_>")
392 }
393}