Skip to main content

cranpose_core/
hooks.rs

1use crate::composer_context;
2use crate::location_key;
3use crate::owned::Owned;
4use crate::runtime;
5use crate::state::{
6    DerivedState, MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State,
7};
8use std::hash::Hash;
9use std::rc::Rc;
10
11pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Owned<T> {
12    composer_context::with_composer(|composer| composer.remember(init))
13}
14
15/// Returns a [`MutableState`] that always holds the latest value.
16///
17/// The state **reference** is stable across recompositions; only the **value** updates.
18/// This allows closures to capture a stable reference while reading fresh values.
19///
20/// # Use Case
21/// Use when a `remember`ed closure needs to read a value that changes each recomposition
22/// without recreating the closure itself.
23///
24/// # Example
25/// ```rust,ignore
26/// let config = build_config(); // Rebuilt each recomposition
27/// let config_state = rememberUpdatedState(config);
28///
29/// // This closure is created once, reads latest config via state
30/// let callback = remember(|| {
31///     let cfg = config_state;
32///     Rc::new(move || do_something(&cfg.value()))
33/// }).with(|c| c.clone());
34/// ```
35///
36/// # JC Equivalent
37/// ```kotlin
38/// @Composable
39/// fun <T> rememberUpdatedState(newValue: T): State<T> =
40///     remember { mutableStateOf(newValue) }.apply { value = newValue }
41/// ```
42#[allow(non_snake_case)]
43pub fn rememberUpdatedState<T: Clone + 'static>(value: T) -> MutableState<T> {
44    composer_context::with_composer(|composer| {
45        let runtime = composer.runtime_handle();
46        let state = composer.remember(|| OwnedMutableState::with_runtime(value.clone(), runtime));
47        state.with(|s| {
48            s.set(value);
49            s.handle()
50        })
51    })
52}
53
54#[cfg(feature = "internal")]
55#[allow(non_snake_case)]
56pub fn withFrameNanos(
57    callback: impl FnOnce(u64) + 'static,
58) -> crate::internal::FrameCallbackRegistration {
59    composer_context::with_composer(|composer| {
60        composer
61            .runtime_handle()
62            .frame_clock()
63            .with_frame_nanos(callback)
64    })
65}
66
67#[cfg(feature = "internal")]
68#[allow(non_snake_case)]
69pub fn withFrameMillis(
70    callback: impl FnOnce(u64) + 'static,
71) -> crate::internal::FrameCallbackRegistration {
72    composer_context::with_composer(|composer| {
73        composer
74            .runtime_handle()
75            .frame_clock()
76            .with_frame_millis(callback)
77    })
78}
79
80/// Creates a new `MutableState` initialized with the given value.
81///
82/// `MutableState` is a cheap copyable observable handle. Reads are tracked by the
83/// current composer or snapshot, and writes trigger recomposition of scopes that
84/// read it.
85///
86/// # When to use
87/// Use `mutableStateOf` when:
88/// 1.  You are creating state properties inside a struct or class (not a composable function).
89/// 2.  You are implementing a custom state management solution.
90///
91/// **If you are inside a `#[composable]` function, use [`useState`] instead.**
92/// `useState` wraps `mutableStateOf` in `remember`, ensuring the state persists
93/// across recompositions. Using `mutableStateOf` directly in a composable will
94/// recreated the state on every frame, losing data.
95///
96/// # Example
97///
98/// ```rust,ignore
99/// struct MyViewModel {
100///     name: MutableState<String>,
101/// }
102///
103/// impl MyViewModel {
104///     fn new() -> Self {
105///         Self {
106///             name: mutableStateOf("Alice".into()),
107///         }
108///     }
109/// }
110/// ```
111///
112/// This creates a runtime-owned persistent state. If you need the state lifetime
113/// tied to a Rust owner instead, store an [`OwnedMutableState`] or call
114/// [`MutableState::retain`] on a handle returned by [`useState`].
115#[allow(non_snake_case)]
116pub fn mutableStateOf<T: Clone + 'static>(initial: T) -> MutableState<T> {
117    let runtime = composer_context::try_with_composer(|composer| composer.runtime_handle())
118        .or_else(runtime::current_runtime_handle)
119        .expect("mutableStateOf requires an active runtime. Create state inside a composition or after a Runtime is created.");
120    runtime.alloc_persistent_state(initial)
121}
122
123#[allow(non_snake_case)]
124pub fn ownedMutableStateOf<T: Clone + 'static>(initial: T) -> OwnedMutableState<T> {
125    let runtime = composer_context::try_with_composer(|composer| composer.runtime_handle())
126        .or_else(runtime::current_runtime_handle)
127        .expect("ownedMutableStateOf requires an active runtime. Create state inside a composition or after a Runtime is created.");
128    OwnedMutableState::with_runtime(initial, runtime)
129}
130
131/// Like [`mutableStateOf`] but returns `None` if no runtime is available.
132///
133/// Use this when you want to lazily initialize reactive state and gracefully
134/// handle the case where the runtime isn't yet available.
135#[allow(non_snake_case)]
136pub fn try_mutableStateOf<T: Clone + 'static>(initial: T) -> Option<MutableState<T>> {
137    let runtime = composer_context::try_with_composer(|composer| composer.runtime_handle())
138        .or_else(runtime::current_runtime_handle)?;
139    Some(runtime.alloc_persistent_state(initial))
140}
141
142#[allow(non_snake_case)]
143pub fn mutableStateListOf<T, I>(values: I) -> SnapshotStateList<T>
144where
145    T: Clone + 'static,
146    I: IntoIterator<Item = T>,
147{
148    composer_context::with_composer(move |composer| composer.mutable_state_list_of(values))
149}
150
151#[allow(non_snake_case)]
152pub fn mutableStateList<T: Clone + 'static>() -> SnapshotStateList<T> {
153    mutableStateListOf(std::iter::empty::<T>())
154}
155
156#[allow(non_snake_case)]
157pub fn mutableStateMapOf<K, V, I>(pairs: I) -> SnapshotStateMap<K, V>
158where
159    K: Clone + Eq + Hash + 'static,
160    V: Clone + 'static,
161    I: IntoIterator<Item = (K, V)>,
162{
163    composer_context::with_composer(move |composer| composer.mutable_state_map_of(pairs))
164}
165
166#[allow(non_snake_case)]
167pub fn mutableStateMap<K, V>() -> SnapshotStateMap<K, V>
168where
169    K: Clone + Eq + Hash + 'static,
170    V: Clone + 'static,
171{
172    mutableStateMapOf(std::iter::empty::<(K, V)>())
173}
174
175/// A composable hook that creates and remembers a `MutableState`.
176///
177/// This is the primary way to define local state in a composable function.
178/// It combines `remember` and `mutableStateOf`.
179///
180/// # Arguments
181///
182/// * `init` - A closure that provides the initial value. This is only called once
183///   when the composable enters the composition.
184///
185/// # Example
186///
187/// ```rust,ignore
188/// #[composable]
189/// fn Counter() {
190///     // "count" persists across recompositions.
191///     // If we used mutableStateOf directly, it would reset to 0 every frame.
192///     let count = useState(|| 0);
193///
194///     Button(
195///         onClick = move || count.set(count.value() + 1),
196///         || Text(format!("Count: {}", count.value()))
197///     );
198/// }
199/// ```
200#[allow(non_snake_case)]
201pub fn useState<T: Clone + 'static>(init: impl FnOnce() -> T) -> MutableState<T> {
202    composer_context::with_composer(|composer| {
203        let runtime = composer.runtime_handle();
204        composer
205            .remember(|| OwnedMutableState::with_runtime(init(), runtime))
206            .with(|state| state.handle())
207    })
208}
209
210#[allow(non_snake_case)]
211pub fn derivedStateOf<T: 'static + Clone>(compute: impl Fn() -> T + 'static) -> State<T> {
212    composer_context::with_composer(|composer| {
213        let key = location_key(file!(), line!(), column!());
214        composer.with_group(key, |composer| {
215            let should_recompute = composer
216                .current_recranpose_scope()
217                .map(|scope| scope.should_recompose())
218                .unwrap_or(true);
219            let runtime = composer.runtime_handle();
220            let compute_rc: Rc<dyn Fn() -> T> = Rc::new(compute);
221            let derived =
222                composer.remember(|| DerivedState::new(runtime.clone(), compute_rc.clone()));
223            derived.update(|derived| {
224                derived.set_compute(compute_rc.clone());
225                if should_recompute {
226                    derived.recompute();
227                }
228            });
229            derived.with(|derived| derived.state.as_state())
230        })
231    })
232}