sycamore_web/
stable_counter.rs

1//! Provides a counter that is stable across client and server side. Useful for generating IDs for
2//! accessibility among other things.
3//!
4//! This works internally by using the context API to store the current value of the counter.
5
6use sycamore_reactive::{use_context_or_else, use_global_scope, Signal};
7
8#[derive(Debug, Default, Clone, Copy)]
9struct CounterValue {
10    next: Signal<u32>,
11}
12
13/// Get the next counter value. This is stable across client and server side.
14///
15/// The counter is stored in the global scope so that it is shared across the entire app. It is
16/// initialized to 0 and incremented everytime this function is called.
17pub fn use_stable_counter() -> u32 {
18    let global_scope = use_global_scope();
19    let counter = global_scope.run_in(|| use_context_or_else(CounterValue::default));
20
21    let next = counter.next.get();
22    counter.next.set(next + 1);
23    next
24}
25
26#[cfg(test)]
27mod tests {
28    use sycamore_reactive::*;
29
30    use super::*;
31
32    #[test]
33    fn stable_counter_works() {
34        let _ = create_root(|| {
35            let counter = use_stable_counter();
36            assert_eq!(counter, 0);
37
38            let counter = use_stable_counter();
39            assert_eq!(counter, 1);
40
41            let _ = create_child_scope(|| {
42                let counter = use_stable_counter();
43                assert_eq!(counter, 2);
44            });
45
46            let counter = use_stable_counter();
47            assert_eq!(counter, 3);
48        });
49    }
50}