1use 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::*;
37pub use yew_macro::function_component as component;
64#[deprecated(since = "0.22.0", note = "renamed to `#[component]")]
66pub use yew_macro::function_component;
67pub use yew_macro::hook;
69
70type ReRender = Rc<dyn Fn()>;
71
72#[cfg(any(feature = "hydration", feature = "ssr"))]
74pub(crate) trait PreparedState {
75 #[cfg(feature = "ssr")]
76 fn prepare(&self) -> String;
77}
78
79pub(crate) trait Effect {
81 fn rendered(&self) {}
82}
83
84pub 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 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 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 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 #[cfg(debug_assertions)]
223 fn assert_hook_context(&mut self, render_ok: bool) {
224 match (render_ok, self.total_hook_counter) {
227 (true, None) => {
230 self.total_hook_counter = Some(self.counter);
231 }
232 (false, None) => {}
235
236 (true, Some(total_hook_counter)) => assert_eq!(
239 total_hook_counter, self.counter,
240 "Hooks are called conditionally."
241 ),
242
243 (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 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
297pub trait FunctionProvider {
299 type Properties: Properties + PartialEq;
301
302 fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult;
307}
308
309#[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 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 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 pub fn rendered(&self) {
372 let hook_ctx = self.hook_ctx.borrow();
373 hook_ctx.run_effects();
374 }
375
376 pub fn destroy(&self) {
378 let mut hook_ctx = self.hook_ctx.borrow_mut();
379 hook_ctx.drain_states();
380 }
381
382 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}