Remove a number of hooks

This commit is contained in:
Jonathan Kelley 2024-01-15 14:51:34 -08:00
parent fe12b1062f
commit b291a5c0b0
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
10 changed files with 4 additions and 1194 deletions

View file

@ -1,22 +0,0 @@
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app);
}
fn app() -> Element {
let onclick = use_callback!(move |_| async move {
let res = reqwest::get("https://dog.ceo/api/breeds/list/all")
.await
.unwrap()
.text()
.await
.unwrap();
println!("{res:#?}, ");
});
rsx! {
button { onclick, "Click me!" }
}
}

View file

@ -97,7 +97,7 @@ fn ClientAdd() -> Element {
label { "for": "first_name", "First Name" }
input {
id: "first_name",
"type": "text",
r#type: "text",
placeholder: "First Name…",
required: "",
value: "{first_name}",
@ -109,7 +109,7 @@ fn ClientAdd() -> Element {
label { "for": "last_name", "Last Name" }
input {
id: "last_name",
"type": "text",
r#type: "text",
placeholder: "Last Name…",
required: "",
value: "{last_name}",
@ -128,7 +128,7 @@ fn ClientAdd() -> Element {
}
div { class: "pure-controls",
button { "type": "submit", class: "pure-button pure-button-primary", "Save" }
button { r#type: "submit", class: "pure-button pure-button-primary", "Save" }
Link { to: Route::ClientList {}, class: "pure-button pure-button-primary red", "Cancel" }
}
}

View file

@ -29,10 +29,7 @@ fn app() -> Element {
ul { flex: "50%",
for cur_breed in breed_list.message.keys().take(10) {
li { key: "{cur_breed}",
button {
onclick: move |_| breed.set(cur_breed.clone()),
"{cur_breed}"
}
button { onclick: move |_| breed.set(cur_breed.clone()), "{cur_breed}" }
}
}
}

View file

@ -1,48 +0,0 @@
use dioxus_core::ScopeState;
use std::future::Future;
#[macro_export]
macro_rules! use_callback {
// ($cx:ident, || || $($rest:tt)*) => { use_callback( $cx, (), |_| $($rest)* ) };
// ($cx:ident, || || $($rest:tt)*) => { use_callback( $cx, (), |_| $($rest)* ) };
($cx:ident, || $($rest:tt)*) => {
use_callback(
$cx,
move || $($rest)*
)
};
($cx:ident, |$($args:tt),* | $($rest:tt)*) => {
use_callback(
$cx,
move || $($rest)*
)
};
($cx:ident, $($rest:tt)*) => {
use_callback(
$cx,
move || $($rest)*
)
};
}
pub fn use_callback<T, R, F>(, make: impl FnOnce() -> R) -> impl FnMut(T) + '_
where
R: FnMut(T) -> F + 'static,
F: Future<Output = ()> + 'static,
{
let mut hook = make();
move |evt| cx.spawn(hook(evt))
}
fn _it_works() {
let _p = use_callback(|| {
|()| async {
//
}
});
// let p = use_callback!(|| |evt| async {
// //
// });
}

View file

@ -1,75 +0,0 @@
use std::rc::Rc;
use dioxus_core::prelude::*;
/// Store constant state between component renders.
///
/// UseConst allows you to store state that is initialized once and then remains constant across renders.
/// You can only get an immutable reference after initalization.
/// This can be useful for values that don't need to update reactively, thus can be memoized easily
///
/// ```rust, ignore
/// struct ComplexData(i32);
///
/// fn Component() -> Element {
/// let id = use_const(|| ComplexData(100));
///
/// rsx! {
/// div { "{id.0}" }
/// })
/// }
/// ```
#[must_use]
pub fn use_const<T: 'static>
initial_state_fn: impl FnOnce() -> T,
) -> &UseConst<T> {
cx.use_hook(|| UseConst {
value: Rc::new(initial_state_fn()),
})
}
#[derive(Clone)]
pub struct UseConst<T> {
value: Rc<T>,
}
impl<T> PartialEq for UseConst<T> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.value, &other.value)
}
}
impl<T: core::fmt::Display> core::fmt::Display for UseConst<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl<T> UseConst<T> {
pub fn get_rc(&self) -> &Rc<T> {
&self.value
}
}
impl<T> std::ops::Deref for UseConst<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value.as_ref()
}
}
#[test]
fn use_const_makes_sense() {
#[allow(unused)]
fn app() -> Element {
let const_val = use_const(|| vec![0, 1, 2, 3]);
assert!(const_val[0] == 0);
// const_val.remove(0); // Cannot Compile, cannot get mutable reference now
None
}
}

View file

@ -1,174 +0,0 @@
use dioxus_core::{ScopeState, Task};
use std::{
any::Any,
cell::{Cell, RefCell},
future::Future,
rc::Rc,
};
use crate::UseFutureDep;
/// A hook that provides a future that executes after the hooks have been applied.
///
/// Whenever the hooks dependencies change, the future will be re-evaluated.
/// If a future is pending when the dependencies change, the previous future
/// will be allowed to continue.
///
/// **Note:** If your dependency list is always empty, use [`use_on_create`](crate::use_on_create).
///
/// ## Arguments
///
/// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`.
/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. That future may return nothing or a closure that will be executed when the dependencies change to clean up the effect.
///
/// ## Examples
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// #[component]
/// fn Profile(id: usize) -> Element {
/// let name = use_signal(|| None);
///
/// // Only fetch the user data when the id changes.
/// use_effect((id,), |(id,)| {
/// to_owned![name];
/// async move {
/// let user = fetch_user(id).await;
/// name.set(user.name);
/// }
/// });
///
/// // Only fetch the user data when the id changes.
/// use_effect((id,), |(id,)| {
/// to_owned![name];
/// async move {
/// let user = fetch_user(id).await;
/// name.set(user.name);
/// move || println!("Cleaning up from {}", id)
/// }
/// });
///
/// let name = name.get().clone().unwrap_or("Loading...".to_string());
///
/// render!(
/// p { "{name}" }
/// )
/// }
///
/// #[component]
/// fn App() -> Element {
/// render!(Profile { id: 0 })
/// }
/// ```
pub fn use_effect<T, R, D>(, dependencies: D, future: impl FnOnce(D::Out) -> R)
where
D: UseFutureDep,
R: UseEffectReturn<T>,
{
struct UseEffect {
needs_regen: bool,
task: Cell<Option<Task>>,
dependencies: Vec<Box<dyn Any>>,
cleanup: UseEffectCleanup,
}
impl Drop for UseEffect {
fn drop(&mut self) {
if let Some(cleanup) = self.cleanup.borrow_mut().take() {
cleanup();
}
}
}
let state = cx.use_hook(move || UseEffect {
needs_regen: true,
task: Cell::new(None),
dependencies: Vec::new(),
cleanup: Rc::new(RefCell::new(None)),
});
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen {
// Call the cleanup function if it exists
if let Some(cleanup) = state.cleanup.borrow_mut().take() {
cleanup();
}
// We don't need regen anymore
state.needs_regen = false;
// Create the new future
let return_value = future(dependencies.out());
let task = return_value.apply(state.cleanup.clone(), cx);
state.task.set(Some(task));
}
}
type UseEffectCleanup = Rc<RefCell<Option<Box<dyn FnOnce()>>>>;
/// Something that can be returned from a `use_effect` hook.
pub trait UseEffectReturn<T> {
fn apply(self, oncleanup: UseEffectCleanup, ) -> Task;
}
impl<T> UseEffectReturn<()> for T
where
T: Future<Output = ()> + 'static,
{
fn apply(self, _: UseEffectCleanup, ) -> Task {
cx.push_future(self)
}
}
#[doc(hidden)]
pub struct CleanupFutureMarker;
impl<T, F> UseEffectReturn<CleanupFutureMarker> for T
where
T: Future<Output = F> + 'static,
F: FnOnce() + 'static,
{
fn apply(self, oncleanup: UseEffectCleanup, ) -> Task {
cx.push_future(async move {
let cleanup = self.await;
*oncleanup.borrow_mut() = Some(Box::new(cleanup) as Box<dyn FnOnce()>);
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused)]
#[test]
fn test_use_future() {
use dioxus_core::prelude::*;
struct MyProps {
a: String,
b: i32,
c: i32,
d: i32,
e: i32,
}
fn app(cx: Scope<MyProps>) -> Element {
// should only ever run once
use_effect((), |_| async move {
//
});
// runs when a is changed
use_effect((&cx.props.a,), |(a,)| async move {
//
});
// runs when a or b is changed
use_effect((&cx.props.a, &cx.props.b), |(a, b)| async move {
//
});
todo!()
}
}
}

View file

@ -1,92 +0,0 @@
//! When building complex components, it's occasionally useful to dip into a pure MVC pattern instead of the
//! React hooks pattern. Hooks are useful to abstract over some reusable logic, but many models are not reusable
//! in the same way that hooks are.
//!
//! In these cases, we provide `use_model` - a convenient way of abstracting over some state and async functions.
use dioxus_core::prelude::ScopeState;
use std::{
cell::{Cell, Ref, RefCell, RefMut},
future::Future,
marker::PhantomData,
pin::Pin,
rc::Rc,
};
pub fn use_model<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> UseModel<'a, T> {
let inner = cx.use_hook(|| UseModelInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
// tasks: RefCell::new(Vec::new()),
});
inner.update_scheduled.set(false);
UseModel { inner }
}
pub struct UseModel<'a, T> {
inner: &'a UseModelInner<T>,
}
struct UseModelInner<T> {
update_scheduled: Cell<bool>,
update_callback: Rc<dyn Fn()>,
value: RefCell<T>,
// tasks: RefCell<Vec<ModelTask>>,
}
type ModelTask = Pin<Box<dyn Future<Output = ()> + 'static>>;
impl<'a, T: 'static> UseModel<'a, T> {
pub fn read(&self) -> Ref<'_, T> {
self.inner.value.borrow()
}
pub fn write(&self) -> RefMut<'_, T> {
self.needs_update();
self.inner.value.borrow_mut()
}
/// Allows the ability to write the value without forcing a re-render
pub fn write_silent(&self) -> RefMut<'_, T> {
self.inner.value.borrow_mut()
}
pub fn needs_update(&self) {
if !self.inner.update_scheduled.get() {
self.inner.update_scheduled.set(true);
(self.inner.update_callback)();
}
}
pub fn set(&self, new: T) {
*self.inner.value.borrow_mut() = new;
self.needs_update();
}
pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
(self.read(), self)
}
pub fn start(&self, _f: impl FnOnce() -> ModelTask) {
todo!()
}
}
// keep a coroutine going
pub fn use_model_coroutine<'a, T, F: Future<Output = ()> + 'static>(
cx: &'a ScopeState,
_model: UseModel<T>,
_f: impl FnOnce(AppModels) -> F,
) -> UseModelCoroutine {
cx.use_hook(|| UseModelTaskInner {
task: Default::default(),
});
todo!()
}
impl<T> Copy for UseModel<'_, T> {}
impl<'a, T> Clone for UseModel<'a, T> {
fn clone(&self) -> Self {
Self { inner: self.inner }
}
}

View file

@ -1,252 +0,0 @@
use dioxus_core::ScopeState;
use std::{
cell::{Cell, Ref, RefCell, RefMut},
rc::Rc,
sync::Arc,
};
/// `use_ref` is a key foundational hook for storing state in Dioxus.
///
/// It is different that `use_state` in that the value stored is not "immutable".
/// Instead, UseRef is designed to store larger values that will be mutated at will.
///
/// ## Writing Values
///
/// Generally, `use_ref` is just a wrapper around a RefCell that tracks mutable
/// writes through the `write` method. Whenever `write` is called, the component
/// that initialized the hook will be marked as "dirty".
///
/// ```rust, no_run
/// let val = use_signal(|| HashMap::<u32, String>::new());
///
/// // using `write` will give us a `RefMut` to the inner value, which we can call methods on
/// // This marks the component as "dirty"
/// val.write().insert(1, "hello".to_string());
/// ```
///
/// You can avoid this default behavior with `write_silent`
///
/// ```rust, no_run
/// // with `write_silent`, the component will not be re-rendered
/// val.write_silent().insert(2, "goodbye".to_string());
/// ```
///
/// ## Reading Values
///
/// To read values out of the refcell, you can use the `read` method which will retrun a `Ref`.
///
/// ```rust, no_run
/// let map: Ref<_> = val.read();
///
/// let item = map.get(&1);
/// ```
///
/// To get an &T out of the RefCell, you need to "reborrow" through the Ref:
///
/// ```rust, no_run
/// let read = val.read();
/// let map = &*read;
/// ```
///
/// ## Collections and iteration
///
/// A common usecase for `use_ref` is to store a large amount of data in a component.
/// Typically this will be a collection like a HashMap or a Vec. To create new
/// elements from the collection, we can use `read()` directly in our rsx!.
///
/// ```rust, no_run
/// rsx!{
/// val.read().iter().map(|(k, v)| {
/// rsx!{ key: "{k}", "{v}" }
/// })
/// }
/// ```
///
/// If you are generating elements outside of `rsx!` then you might need to call
/// "render" inside the iterator. For some cases you might need to collect into
/// a temporary Vec.
///
/// ```rust, no_run
/// let items = val.read().iter().map(|(k, v)| {
/// rsx!{ key: "{k}", "{v}" })
/// });
///
/// // collect into a Vec
///
/// let items: Vec<Element> = items.collect();
/// ```
///
/// ## Use in Async
///
/// To access values from a `UseRef` in an async context, you need to detach it
/// from the current scope's lifetime, making it a `'static` value. This is done
/// by simply calling `to_owned` or `clone`.
///
/// ```rust, no_run
/// let val = use_signal(|| HashMap::<u32, String>::new());
///
/// cx.spawn({
/// let val = val.clone();
/// async move {
/// some_work().await;
/// val.write().insert(1, "hello".to_string());
/// }
/// })
/// ```
///
/// If you're working with lots of values like UseState and UseRef, you can use the
/// `to_owned!` macro to make it easier to write the above code.
///
/// ```rust, no_run
/// let val1 = use_signal(|| HashMap::<u32, String>::new());
/// let val2 = use_signal(|| HashMap::<u32, String>::new());
/// let val3 = use_signal(|| HashMap::<u32, String>::new());
///
/// cx.spawn({
/// to_owned![val1, val2, val3];
/// async move {
/// some_work().await;
/// val.write().insert(1, "hello".to_string());
/// }
/// })
/// ```
#[must_use]
pub fn use_ref<T: 'static>(nitialize_refcell: impl FnOnce() -> T) -> Signal<T> {
let hook = cx.use_hook(|| UseRef {
update: cx.schedule_update(),
value: Rc::new(RefCell::new(initialize_refcell())),
dirty: Rc::new(Cell::new(false)),
gen: 0,
});
if hook.dirty.get() {
hook.gen += 1;
hook.dirty.set(false);
}
hook
}
/// A type created by the [`use_ref`] hook. See its documentation for more details.
pub struct UseRef<T> {
update: Arc<dyn Fn()>,
value: Rc<RefCell<T>>,
dirty: Rc<Cell<bool>>,
gen: usize,
}
impl<T> Clone for UseRef<T> {
fn clone(&self) -> Self {
Self {
update: self.update.clone(),
value: self.value.clone(),
dirty: self.dirty.clone(),
gen: self.gen,
}
}
}
impl<T> UseRef<T> {
/// Read the value in the RefCell into a `Ref`. If this method is called
/// while other values are still being `read` or `write`, then your app will crash.
///
/// Be very careful when working with this method. If you can, consider using
/// the `with` and `with_mut` methods instead, choosing to render Elements
/// during the read calls.
pub fn read(&self) -> Ref<'_, T> {
self.value.borrow()
}
/// Mutably unlock the value in the RefCell. This will mark the component as "dirty"
///
/// Uses to `write` should be as short as possible.
///
/// Be very careful when working with this method. If you can, consider using
/// the `with` and `with_mut` methods instead, choosing to render Elements
/// during the read and write calls.
pub fn write(&self) -> RefMut<'_, T> {
self.needs_update();
self.value.borrow_mut()
}
/// Set the curernt value to `new_value`. This will mark the component as "dirty"
///
/// This change will propagate immediately, so any other contexts that are
/// using this RefCell will also be affected. If called during an async context,
/// the component will not be re-rendered until the next `.await` call.
pub fn set(&self, new: T) {
*self.value.borrow_mut() = new;
self.needs_update();
}
/// Mutably unlock the value in the RefCell. This will not mark the component as dirty.
/// This is useful if you want to do some work without causing the component to re-render.
///
/// Uses to `write` should be as short as possible.
///
/// Be very careful when working with this method. If you can, consider using
/// the `with` and `with_mut` methods instead, choosing to render Elements
pub fn write_silent(&self) -> RefMut<'_, T> {
self.value.borrow_mut()
}
/// Take a reference to the inner value termporarily and produce a new value
///
/// Note: You can always "reborrow" the value through the RefCell.
/// This method just does it for you automatically.
///
/// ```rust, no_run
/// let val = use_signal(|| HashMap::<u32, String>::new());
///
///
/// // use reborrowing
/// let inner = &*val.read();
///
/// // or, be safer and use `with`
/// val.with(|i| println!("{:?}", i));
/// ```
pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
immutable_callback(&*self.read())
}
/// Take a reference to the inner value termporarily and produce a new value,
/// modifying the original in place.
///
/// Note: You can always "reborrow" the value through the RefCell.
/// This method just does it for you automatically.
///
/// ```rust, no_run
/// let val = use_signal(|| HashMap::<u32, String>::new());
///
///
/// // use reborrowing
/// let inner = &mut *val.write();
///
/// // or, be safer and use `with`
/// val.with_mut(|i| i.insert(1, "hi"));
/// ```
pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
mutable_callback(&mut *self.write())
}
/// Call the inner callback to mark the originator component as dirty.
///
/// This will cause the component to be re-rendered after the current scope
/// has ended or the current async task has been yielded through await.
pub fn needs_update(&self) {
self.dirty.set(true);
(self.update)();
}
}
// UseRef memoizes not on value but on cell
// Memoizes on "generation" - so it will cause a re-render if the value changes
impl<T> PartialEq for UseRef<T> {
fn eq(&self, other: &Self) -> bool {
if Rc::ptr_eq(&self.value, &other.value) {
self.gen == other.gen
} else {
false
}
}
}

View file

@ -1,524 +0,0 @@
#![warn(clippy::pedantic)]
use dioxus_core::prelude::*;
use std::{
cell::{RefCell, RefMut},
fmt::{Debug, Display},
ops::{Add, Div, Mul, Not, Sub},
rc::Rc,
sync::Arc,
};
/// Store state between component renders.
///
/// ## Dioxus equivalent of useState, designed for Rust
///
/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
/// modify state between component renders. When the state is updated, the component will re-render.
///
///
/// ```ignore
/// const Example: Component = |cx| {
/// let count = use_signal(|| 0);
///
/// rsx! {
/// div {
/// h1 { "Count: {count}" }
/// button { onclick: move |_| *count.modify() += 1, "Increment" }
/// button { onclick: move |_| *count.modify() -= 1, "Decrement" }
/// }
/// ))
/// }
/// ```
#[must_use]
pub fn use_state<T: 'static>
initial_state_fn: impl FnOnce() -> T,
) -> &UseState<T> {
let hook = cx.use_hook(move || {
let current_val = Rc::new(initial_state_fn());
let update_callback = cx.schedule_update();
let slot = Rc::new(RefCell::new(current_val.clone()));
let setter = Rc::new({
to_owned![update_callback, slot];
move |new| {
{
let mut slot = slot.borrow_mut();
// if there's only one reference (weak or otherwise), we can just swap the values
// Typically happens when the state is set multiple times - we don't want to create a new Rc for each new value
if let Some(val) = Rc::get_mut(&mut slot) {
*val = new;
} else {
*slot = Rc::new(new);
}
}
update_callback();
}
});
UseState {
current_val,
update_callback,
setter,
slot,
}
});
hook.current_val = hook.slot.borrow().clone();
hook
}
pub struct UseState<T: 'static> {
pub(crate) current_val: Rc<T>,
pub(crate) update_callback: Arc<dyn Fn()>,
pub(crate) setter: Rc<dyn Fn(T)>,
pub(crate) slot: Rc<RefCell<Rc<T>>>,
}
impl<T: 'static> UseState<T> {
/// Set the state to a new value.
pub fn set(&self, new: T) {
(self.setter)(new);
}
/// Get the current value of the state by cloning its container Rc.
///
/// This is useful when you are dealing with state in async contexts but need
/// to know the current value. You are not given a reference to the state.
///
/// # Examples
/// An async context might need to know the current value:
///
/// ```rust, ignore
/// fn component() -> Element {
/// let count = use_signal(|| 0);
/// cx.spawn({
/// let set_count = count.to_owned();
/// async move {
/// let current = set_count.current();
/// }
/// })
/// }
/// ```
#[must_use]
pub fn current(&self) -> Rc<T> {
self.slot.borrow().clone()
}
/// Get the `setter` function directly without the `UseState` wrapper.
///
/// This is useful for passing the setter function to other components.
///
/// However, for most cases, calling `to_owned` on the state is the
/// preferred way to get "another" state handle.
///
///
/// # Examples
/// A component might require an `Rc<dyn Fn(T)>` as an input to set a value.
///
/// ```rust, ignore
/// fn component() -> Element {
/// let value = use_signal(|| 0);
///
/// rsx!{
/// Component {
/// handler: value.setter()
/// }
/// }
/// }
/// ```
#[must_use]
pub fn setter(&self) -> Rc<dyn Fn(T)> {
self.setter.clone()
}
/// Set the state to a new value, using the current state value as a reference.
///
/// This is similar to passing a closure to React's `set_value` function.
///
/// # Examples
///
/// Basic usage:
/// ```rust, ignore
/// # use dioxus_core::prelude::*;
/// # use dioxus_hooks::*;
/// fn component() -> Element {
/// let value = use_signal(|| 0);
///
/// // to increment the value
/// value.modify(|v| v + 1);
///
/// // usage in async
/// cx.spawn({
/// let value = value.to_owned();
/// async move {
/// value.modify(|v| v + 1);
/// }
/// });
///
/// # todo!()
/// }
/// ```
pub fn modify(&self, f: impl FnOnce(&T) -> T) {
let new_val = {
let current = self.slot.borrow();
f(current.as_ref())
};
(self.setter)(new_val);
}
/// Get the value of the state when this handle was created.
///
/// This method is useful when you want an `Rc` around the data to cheaply
/// pass it around your app.
///
/// ## Warning
///
/// This will return a stale value if used within async contexts.
///
/// Try `current` to get the real current value of the state.
///
/// ## Example
///
/// ```rust, ignore
/// # use dioxus_core::prelude::*;
/// # use dioxus_hooks::*;
/// fn component() -> Element {
/// let value = use_signal(|| 0);
///
/// let as_rc = value.get();
/// assert_eq!(as_rc.as_ref(), &0);
///
/// # todo!()
/// }
/// ```
#[must_use]
pub fn get(&self) -> &T {
&self.current_val
}
#[must_use]
pub fn get_rc(&self) -> &Rc<T> {
&self.current_val
}
/// Mark the component that create this [`UseState`] as dirty, forcing it to re-render.
///
/// ```rust, ignore
/// fn component() -> Element {
/// let count = use_signal(|| 0);
/// cx.spawn({
/// let count = count.to_owned();
/// async move {
/// // for the component to re-render
/// count.needs_update();
/// }
/// })
/// }
/// ```
pub fn needs_update(&self) {
(self.update_callback)();
}
}
impl<T: Clone> UseState<T> {
/// Get a mutable handle to the value by calling `ToOwned::to_owned` on the
/// current value.
///
/// This is essentially cloning the underlying value and then setting it,
/// giving you a mutable handle in the process. This method is intended for
/// types that are cheaply cloneable.
///
/// If you are comfortable dealing with `RefMut`, then you can use `make_mut` to get
/// the underlying slot. However, be careful with `RefMut` since you might panic
/// if the `RefCell` is left open.
///
/// # Examples
///
/// ```rust, ignore
/// let val = use_signal(|| 0);
///
/// val.with_mut(|v| *v = 1);
/// ```
pub fn with_mut(&self, apply: impl FnOnce(&mut T)) {
let mut slot = self.slot.borrow_mut();
let mut inner = slot.as_ref().to_owned();
apply(&mut inner);
if let Some(new) = Rc::get_mut(&mut slot) {
*new = inner;
} else {
*slot = Rc::new(inner);
}
self.needs_update();
}
/// Get a mutable handle to the value by calling `ToOwned::to_owned` on the
/// current value.
///
/// This is essentially cloning the underlying value and then setting it,
/// giving you a mutable handle in the process. This method is intended for
/// types that are cheaply cloneable.
///
/// # Warning
/// Be careful with `RefMut` since you might panic if the `RefCell` is left open!
///
/// # Examples
///
/// ```rust, ignore
/// let val = use_signal(|| 0);
///
/// *val.make_mut() += 1;
/// ```
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn make_mut(&self) -> RefMut<T> {
let mut slot = self.slot.borrow_mut();
self.needs_update();
if Rc::strong_count(&*slot) > 0 {
*slot = Rc::new(slot.as_ref().to_owned());
}
RefMut::map(slot, |rc| Rc::get_mut(rc).expect("the hard count to be 0"))
}
/// Convert this handle to a tuple of the value and the handle itself.
#[must_use]
pub fn split(&self) -> (&T, &Self) {
(&self.current_val, self)
}
}
impl<T: 'static> Clone for UseState<T> {
fn clone(&self) -> Self {
UseState {
current_val: self.current_val.clone(),
update_callback: self.update_callback.clone(),
setter: self.setter.clone(),
slot: self.slot.clone(),
}
}
}
impl<T: 'static + Display> std::fmt::Display for UseState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.current_val)
}
}
impl<T: std::fmt::Binary> std::fmt::Binary for UseState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:b}", self.current_val.as_ref())
}
}
impl<T: PartialEq> PartialEq<T> for UseState<T> {
fn eq(&self, other: &T) -> bool {
self.current_val.as_ref() == other
}
}
// todo: this but for more interesting conrete types
impl PartialEq<bool> for &UseState<bool> {
fn eq(&self, other: &bool) -> bool {
self.current_val.as_ref() == other
}
}
impl<T> PartialEq<UseState<T>> for UseState<T> {
fn eq(&self, other: &UseState<T>) -> bool {
Rc::ptr_eq(&self.current_val, &other.current_val)
}
}
impl<T: std::cmp::PartialOrd> PartialOrd<T> for UseState<T> {
fn ge(&self, other: &T) -> bool {
*self.current_val >= *other
}
fn gt(&self, other: &T) -> bool {
*self.current_val > *other
}
fn le(&self, other: &T) -> bool {
*self.current_val <= *other
}
fn lt(&self, other: &T) -> bool {
*self.current_val < *other
}
fn partial_cmp(&self, other: &T) -> Option<std::cmp::Ordering> {
(*self.current_val).partial_cmp(other)
}
}
impl<T: std::cmp::PartialOrd> PartialOrd<UseState<T>> for UseState<T> {
fn ge(&self, other: &UseState<T>) -> bool {
self.current_val >= other.current_val
}
fn gt(&self, other: &UseState<T>) -> bool {
self.current_val > other.current_val
}
fn le(&self, other: &UseState<T>) -> bool {
self.current_val <= other.current_val
}
fn lt(&self, other: &UseState<T>) -> bool {
self.current_val < other.current_val
}
fn partial_cmp(&self, other: &UseState<T>) -> Option<std::cmp::Ordering> {
self.current_val.partial_cmp(&other.current_val)
}
}
impl<T: Debug> Debug for UseState<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.current_val)
}
}
impl<T> std::ops::Deref for UseState<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.current_val.as_ref()
}
}
impl<T: Not + Copy> std::ops::Not for &UseState<T> {
type Output = <T as std::ops::Not>::Output;
fn not(self) -> Self::Output {
self.current_val.not()
}
}
impl<T: Not + Copy> std::ops::Not for UseState<T> {
type Output = <T as std::ops::Not>::Output;
fn not(self) -> Self::Output {
self.current_val.not()
}
}
impl<T: std::ops::Add + Copy> std::ops::Add<T> for &UseState<T> {
type Output = <T as std::ops::Add>::Output;
fn add(self, other: T) -> Self::Output {
*self.current_val.as_ref() + other
}
}
impl<T: std::ops::Sub + Copy> std::ops::Sub<T> for &UseState<T> {
type Output = <T as std::ops::Sub>::Output;
fn sub(self, other: T) -> Self::Output {
*self.current_val.as_ref() - other
}
}
impl<T: std::ops::Div + Copy> std::ops::Div<T> for &UseState<T> {
type Output = <T as std::ops::Div>::Output;
fn div(self, other: T) -> Self::Output {
*self.current_val.as_ref() / other
}
}
impl<T: std::ops::Mul + Copy> std::ops::Mul<T> for &UseState<T> {
type Output = <T as std::ops::Mul>::Output;
fn mul(self, other: T) -> Self::Output {
*self.current_val.as_ref() * other
}
}
impl<T: Add<Output = T> + Copy> std::ops::AddAssign<T> for &UseState<T> {
fn add_assign(&mut self, rhs: T) {
self.set((*self.current()) + rhs);
}
}
impl<T: Sub<Output = T> + Copy> std::ops::SubAssign<T> for &UseState<T> {
fn sub_assign(&mut self, rhs: T) {
self.set((*self.current()) - rhs);
}
}
impl<T: Mul<Output = T> + Copy> std::ops::MulAssign<T> for &UseState<T> {
fn mul_assign(&mut self, rhs: T) {
self.set((*self.current()) * rhs);
}
}
impl<T: Div<Output = T> + Copy> std::ops::DivAssign<T> for &UseState<T> {
fn div_assign(&mut self, rhs: T) {
self.set((*self.current()) / rhs);
}
}
impl<T: Add<Output = T> + Copy> std::ops::AddAssign<T> for UseState<T> {
fn add_assign(&mut self, rhs: T) {
self.set((*self.current()) + rhs);
}
}
impl<T: Sub<Output = T> + Copy> std::ops::SubAssign<T> for UseState<T> {
fn sub_assign(&mut self, rhs: T) {
self.set((*self.current()) - rhs);
}
}
impl<T: Mul<Output = T> + Copy> std::ops::MulAssign<T> for UseState<T> {
fn mul_assign(&mut self, rhs: T) {
self.set((*self.current()) * rhs);
}
}
impl<T: Div<Output = T> + Copy> std::ops::DivAssign<T> for UseState<T> {
fn div_assign(&mut self, rhs: T) {
self.set((*self.current()) / rhs);
}
}
#[test]
fn api_makes_sense() {
#[allow(unused)]
fn app() -> Element {
let val = use_signal(|| 0);
val.set(0);
val.modify(|v| v + 1);
let real_current = val.current();
match val.get() {
10 => {
val.set(20);
val.modify(|v| v + 1);
}
20 => {}
_ => {
println!("{real_current}");
}
}
cx.spawn({
to_owned![val];
async move {
val.modify(|f| f + 1);
}
});
// cx.render(LazyNodes::new(|f| f.static_text("asd")))
todo!()
}
}