Stores: Externally tracked state and reactive collections (#4483)

* map signals mutably

* boxed signals

* remove mutable borrow type from boxed writable

* improve map mut example

* restore boxed_mut helper

* make boxed readable/writable types copy

* Move away from the ReadOnlySignal alias in examples

* automatically convert Write

* Fix some tests

* ReadSignal and WriteSignal

* move read only signal alias

* make maps copy

* store prototype

* implement derive macro

* Handle generics in the derive macro

* Make Selector boxable

* derive boxed conversion

* Get rid of store type

* Remove hardcoded type from derive macro

* selector -> store

* Refactor store implementation

* more vec methods

* todo mvc store example

* Implement Storable for HashMap

* fix write

* restore sorting logic in todomvc

* derive readable and writable

* improve todomvc store example

* Result store

* Option store

* collect dead subscribers

* fix clippy and formatting

* recognize some common foreign types automatically

* fix foreign stores in props

* slice and array selectors

* clean up store example

* writable is child owned

* box selector

* read and write to builtins

* re-use the proc macro in the derive macro

* Switch to a extension trait based approach

* switch to extension traits in the derive macro

* transposed method

* derive enums

* Expose Store::new

* remove stores from the prelude

* More consistent naming scheme for SelectorScope methods

* document hashmap methods

* document index extension

* document option ext

* document result ext

* document slice extension

* document the vec extension

* make more methods borrow the store mutably

* Move to impls to have higher priority over the builtin write extension traits

* fix deadlock

* finish stores documentation

* Document the store macro

* cleanup docs and organization

* finish derive docs

* fix typo

* fix clippy

* fix doc tests

* fix derive macro with generics

* fix clippy

* test and fix empty structs

* clean up todomvc store example

* default to a boxed store writer to make it easier to type when providing to the context api

* fix cargo doc

* fix clippy in store example

* relax generational box lifetime bounds

* relax bounds

* relax signal bounds

* fix compilation

* read store

* relax bounds on default store impls

* fix enum derive

* Convert to readsignal and writesignal automatically

* simplify type matching logic

* add write store

* unsized stores

* shrink the path vec type

* use more concrete types

* remove opaque types

* remove copy bounds

* use lens as generic name everywhere

* more consistent borrow semantics

* use a concrete type for the signal metadata and mention references in the map docs

* improve todomvc store docs

* document store derive macro internals

* refactor macro

* improve selector node documentation

* fix clippy

* add btreemap

* fix doc tests

* more option methods

* fix doc

* fix clippy

* add a bunch more result methods

* add a bunch of bounds to the iterators

* fix clippy

* remove option from subscribers

* add store macro to generate extension traits

* fix store example

* fix deadlock

* fix docs

* fix doc tests

* fix clippy

* add stores to public api

* add store macro, and then update todomvc_store example

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-08-05 11:30:21 -07:00
1 parent 5591070e92
commit 58c41f8561
57 files changed
+4958 -436

No files matched your search

+366
View File
@@ -0,0 +1,366 @@
use std::{borrow::Borrow, collections::BTreeMap, hash::Hash, iter::FusedIterator};
use crate::{store::Store, ReadStore};
use dioxus_signals::{
AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, ReadableExt, UnsyncStorage,
Writable, WriteLock, WriteSignal,
};
impl<Lens: Readable<Target = BTreeMap<K, V>> + 'static, K: 'static, V: 'static>
Store<BTreeMap<K, V>, Lens>
{
/// Get the length of the BTreeMap. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// assert_eq!(store.len(), 0);
/// store.insert(0, "value".to_string());
/// assert_eq!(store.len(), 1);
/// ```
pub fn len(&self) -> usize {
self.selector().track_shallow();
self.selector().peek().len()
}
/// Check if the BTreeMap is empty. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// assert!(store.is_empty());
/// store.insert(0, "value".to_string());
/// assert!(!store.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.selector().track_shallow();
self.selector().peek().is_empty()
}
/// Iterate over the current entries in the BTreeMap, returning a tuple of the key and a store for the value. This method
/// will track the store shallowly and only cause re-runs when items are added or removed from the map, not when existing
/// values are modified.
///
/// # Example
///
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// store.insert(0, "value1".to_string());
/// store.insert(1, "value2".to_string());
/// for (key, value_store) in store.iter() {
/// println!("{}: {}", key, value_store.read());
/// }
/// ```
pub fn iter(
&self,
) -> impl ExactSizeIterator<Item = (K, Store<V, GetWrite<K, Lens>>)>
+ DoubleEndedIterator
+ FusedIterator
+ '_
where
K: Hash + Ord + Clone,
Lens: Clone,
{
self.selector().track_shallow();
let keys: Vec<_> = self.selector().peek_unchecked().keys().cloned().collect();
keys.into_iter().map(move |key| {
let value = self.clone().get(key.clone()).unwrap();
(key, value)
})
}
/// Get an iterator over the values in the BTreeMap. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// store.insert(0, "value1".to_string());
/// store.insert(1, "value2".to_string());
/// for value_store in store.values() {
/// println!("{}", value_store.read());
/// }
/// ```
pub fn values(
&self,
) -> impl ExactSizeIterator<Item = Store<V, GetWrite<K, Lens>>>
+ DoubleEndedIterator
+ FusedIterator
+ '_
where
K: Hash + Ord + Clone,
Lens: Clone,
{
self.selector().track_shallow();
let keys = self.selector().peek().keys().cloned().collect::<Vec<_>>();
keys.into_iter()
.map(move |key| self.clone().get(key).unwrap())
}
/// Insert a new key-value pair into the BTreeMap. This method will mark the store as shallowly dirty, causing
/// re-runs of any reactive scopes that depend on the shape of the map.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// assert!(store.get(0).is_none());
/// store.insert(0, "value".to_string());
/// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
/// ```
pub fn insert(&mut self, key: K, value: V)
where
K: Ord,
Lens: Writable,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().insert(key, value);
}
/// Remove a key-value pair from the BTreeMap. This method will mark the store as shallowly dirty, causing
/// re-runs of any reactive scopes that depend on the shape of the map or the value of the removed key.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// store.insert(0, "value".to_string());
/// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
/// let removed_value = store.remove(&0);
/// assert_eq!(removed_value, Some("value".to_string()));
/// assert!(store.get(0).is_none());
/// ```
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
Q: ?Sized + Ord + 'static,
K: Borrow<Q> + Ord,
Lens: Writable,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().remove(key)
}
/// Clear the BTreeMap, removing all key-value pairs. This method will mark the store as shallowly dirty,
/// causing re-runs of any reactive scopes that depend on the shape of the map.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// store.insert(1, "value1".to_string());
/// store.insert(2, "value2".to_string());
/// assert_eq!(store.len(), 2);
/// store.clear();
/// assert!(store.is_empty());
/// ```
pub fn clear(&mut self)
where
Lens: Writable,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().clear();
}
/// Retain only the key-value pairs that satisfy the given predicate. This method will mark the store as shallowly dirty,
/// causing re-runs of any reactive scopes that depend on the shape of the map or the values retained.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// store.insert(1, "value1".to_string());
/// store.insert(2, "value2".to_string());
/// store.retain(|key, value| *key == 1);
/// assert_eq!(store.len(), 1);
/// assert!(store.get(1).is_some());
/// assert!(store.get(2).is_none());
/// ```
pub fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool)
where
Lens: Writable,
K: Ord,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().retain(|k, v| f(k, v));
}
/// Check if the BTreeMap contains a key. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// assert!(!store.contains_key(&0));
/// store.insert(0, "value".to_string());
/// assert!(store.contains_key(&0));
/// ```
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
Q: ?Sized + Ord + 'static,
K: Borrow<Q> + Ord,
{
self.selector().track_shallow();
self.selector().peek().contains_key(key)
}
/// Get a store for the value associated with the given key. This method creates a new store scope
/// that tracks just changes to the value associated with the key.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::BTreeMap;
/// let mut store = use_store(|| BTreeMap::new());
/// assert!(store.get(0).is_none());
/// store.insert(0, "value".to_string());
/// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
/// ```
pub fn get<Q>(self, key: Q) -> Option<Store<V, GetWrite<Q, Lens>>>
where
Q: Hash + Ord + 'static,
K: Borrow<Q> + Ord,
{
self.contains_key(&key).then(|| {
self.into_selector()
.hash_child_unmapped(key.borrow())
.map_writer(move |writer| GetWrite {
index: key,
write: writer,
})
.into()
})
}
}
/// A specific index in a `Readable` / `Writable` BTreeMap
#[derive(Clone, Copy)]
pub struct GetWrite<Index, Write> {
index: Index,
write: Write,
}
impl<Index, Write, K, V> Readable for GetWrite<Index, Write>
where
Write: Readable<Target = BTreeMap<K, V>>,
Index: Ord + 'static,
K: Borrow<Index> + Ord + 'static,
{
type Target = V;
type Storage = Write::Storage;
fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
where
Self::Target: 'static,
{
self.write.try_read_unchecked().map(|value| {
Self::Storage::map(value, |value: &Write::Target| {
value
.get(&self.index)
.expect("Tried to access a key that does not exist")
})
})
}
fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
where
Self::Target: 'static,
{
self.write.try_peek_unchecked().map(|value| {
Self::Storage::map(value, |value: &Write::Target| {
value
.get(&self.index)
.expect("Tried to access a key that does not exist")
})
})
}
fn subscribers(&self) -> dioxus_core::Subscribers
where
Self::Target: 'static,
{
self.write.subscribers()
}
}
impl<Index, Write, K, V> Writable for GetWrite<Index, Write>
where
Write: Writable<Target = BTreeMap<K, V>>,
Index: Ord + 'static,
K: Borrow<Index> + Ord + 'static,
{
type WriteMetadata = Write::WriteMetadata;
fn try_write_unchecked(
&self,
) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>
where
Self::Target: 'static,
{
self.write.try_write_unchecked().map(|value| {
WriteLock::map(value, |value: &mut Write::Target| {
value
.get_mut(&self.index)
.expect("Tried to access a key that does not exist")
})
})
}
}
impl<Index, Write, K, V> ::std::convert::From<Store<V, GetWrite<Index, Write>>>
for Store<V, WriteSignal<V>>
where
Write::WriteMetadata: 'static,
Write: Writable<Target = BTreeMap<K, V>, Storage = UnsyncStorage> + 'static,
Index: Ord + 'static,
K: Borrow<Index> + Ord + 'static,
V: 'static,
{
fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
value
.into_selector()
.map_writer(|writer| WriteSignal::new(writer))
.into()
}
}
impl<Index, Write, K, V> ::std::convert::From<Store<V, GetWrite<Index, Write>>> for ReadStore<V>
where
Write: Readable<Target = BTreeMap<K, V>, Storage = UnsyncStorage> + 'static,
Index: Ord + 'static,
K: Borrow<Index> + Ord + 'static,
V: 'static,
{
fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
value
.into_selector()
.map_writer(|writer| ReadSignal::new(writer))
.into()
}
}
+28
View File
@@ -0,0 +1,28 @@
use std::ops::DerefMut;
use crate::{store::Store, MappedStore};
use dioxus_signals::Readable;
impl<Lens, T> Store<T, Lens>
where
Lens: Readable<Target = T> + 'static,
T: DerefMut + 'static,
{
/// Returns a store that dereferences the original value. The dereferenced store shares the same
/// subscriptions and tracking as the original store, but allows you to access the methods of the underlying type.
///
/// # Example
///
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Box::new(vec![1, 2, 3]));
/// let deref_store = store.deref();
/// // The dereferenced store can access the store methods of the underlying type.
/// assert_eq!(deref_store.len(), 3);
/// ```
pub fn deref(self) -> MappedStore<T::Target, Lens> {
let map: fn(&T) -> &T::Target = |value| value.deref();
let map_mut: fn(&mut T) -> &mut T::Target = |value| value.deref_mut();
self.into_selector().map(map, map_mut).into()
}
}
+380
View File
@@ -0,0 +1,380 @@
use std::{
borrow::Borrow,
collections::HashMap,
hash::{BuildHasher, Hash},
iter::FusedIterator,
};
use crate::{store::Store, ReadStore};
use dioxus_signals::{
AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, ReadableExt, UnsyncStorage,
Writable, WriteLock, WriteSignal,
};
impl<Lens: Readable<Target = HashMap<K, V, St>> + 'static, K: 'static, V: 'static, St: 'static>
Store<HashMap<K, V, St>, Lens>
{
/// Get the length of the HashMap. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// assert_eq!(store.len(), 0);
/// store.insert(0, "value".to_string());
/// assert_eq!(store.len(), 1);
/// ```
pub fn len(&self) -> usize {
self.selector().track_shallow();
self.selector().peek().len()
}
/// Check if the HashMap is empty. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// assert!(store.is_empty());
/// store.insert(0, "value".to_string());
/// assert!(!store.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.selector().track_shallow();
self.selector().peek().is_empty()
}
/// Iterate over the current entries in the HashMap, returning a tuple of the key and a store for the value. This method
/// will track the store shallowly and only cause re-runs when items are added or removed from the map, not when existing
/// values are modified.
///
/// # Example
///
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// store.insert(0, "value1".to_string());
/// store.insert(1, "value2".to_string());
/// for (key, value_store) in store.iter() {
/// println!("{}: {}", key, value_store.read());
/// }
/// ```
pub fn iter(
&self,
) -> impl ExactSizeIterator<Item = (K, Store<V, GetWrite<K, Lens>>)>
+ DoubleEndedIterator
+ FusedIterator
+ '_
where
K: Eq + Hash + Clone,
St: BuildHasher,
Lens: Clone,
{
self.selector().track_shallow();
let keys: Vec<_> = self.selector().peek_unchecked().keys().cloned().collect();
keys.into_iter().map(move |key| {
let value = self.clone().get(key.clone()).unwrap();
(key, value)
})
}
/// Get an iterator over the values in the HashMap. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// store.insert(0, "value1".to_string());
/// store.insert(1, "value2".to_string());
/// for value_store in store.values() {
/// println!("{}", value_store.read());
/// }
/// ```
pub fn values(
&self,
) -> impl ExactSizeIterator<Item = Store<V, GetWrite<K, Lens>>>
+ DoubleEndedIterator
+ FusedIterator
+ '_
where
K: Eq + Hash + Clone,
St: BuildHasher,
Lens: Clone,
{
self.selector().track_shallow();
let keys = self.selector().peek().keys().cloned().collect::<Vec<_>>();
keys.into_iter()
.map(move |key| self.clone().get(key).unwrap())
}
/// Insert a new key-value pair into the HashMap. This method will mark the store as shallowly dirty, causing
/// re-runs of any reactive scopes that depend on the shape of the map.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// assert!(store.get(0).is_none());
/// store.insert(0, "value".to_string());
/// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
/// ```
pub fn insert(&mut self, key: K, value: V)
where
K: Eq + Hash,
St: BuildHasher,
Lens: Writable,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().insert(key, value);
}
/// Remove a key-value pair from the HashMap. This method will mark the store as shallowly dirty, causing
/// re-runs of any reactive scopes that depend on the shape of the map or the value of the removed key.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// store.insert(0, "value".to_string());
/// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
/// let removed_value = store.remove(&0);
/// assert_eq!(removed_value, Some("value".to_string()));
/// assert!(store.get(0).is_none());
/// ```
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
Q: ?Sized + Hash + Eq + 'static,
K: Borrow<Q> + Eq + Hash,
St: BuildHasher,
Lens: Writable,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().remove(key)
}
/// Clear the HashMap, removing all key-value pairs. This method will mark the store as shallowly dirty,
/// causing re-runs of any reactive scopes that depend on the shape of the map.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// store.insert(1, "value1".to_string());
/// store.insert(2, "value2".to_string());
/// assert_eq!(store.len(), 2);
/// store.clear();
/// assert!(store.is_empty());
/// ```
pub fn clear(&mut self)
where
Lens: Writable,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().clear();
}
/// Retain only the key-value pairs that satisfy the given predicate. This method will mark the store as shallowly dirty,
/// causing re-runs of any reactive scopes that depend on the shape of the map or the values retained.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// store.insert(1, "value1".to_string());
/// store.insert(2, "value2".to_string());
/// store.retain(|key, value| *key == 1);
/// assert_eq!(store.len(), 1);
/// assert!(store.get(1).is_some());
/// assert!(store.get(2).is_none());
/// ```
pub fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool)
where
Lens: Writable,
{
self.selector().mark_dirty_shallow();
self.selector().write_untracked().retain(|k, v| f(k, v));
}
/// Check if the HashMap contains a key. This method will track the store shallowly and only cause
/// re-runs when items are added or removed from the map, not when existing values are modified.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// assert!(!store.contains_key(&0));
/// store.insert(0, "value".to_string());
/// assert!(store.contains_key(&0));
/// ```
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
Q: ?Sized + Hash + Eq + 'static,
K: Borrow<Q> + Eq + Hash,
St: BuildHasher,
{
self.selector().track_shallow();
self.selector().peek().contains_key(key)
}
/// Get a store for the value associated with the given key. This method creates a new store scope
/// that tracks just changes to the value associated with the key.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// use dioxus::prelude::*;
/// use std::collections::HashMap;
/// let mut store = use_store(|| HashMap::new());
/// assert!(store.get(0).is_none());
/// store.insert(0, "value".to_string());
/// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
/// ```
pub fn get<Q>(self, key: Q) -> Option<Store<V, GetWrite<Q, Lens>>>
where
Q: Hash + Eq + 'static,
K: Borrow<Q> + Eq + Hash,
St: BuildHasher,
{
self.contains_key(&key).then(|| {
self.into_selector()
.hash_child_unmapped(key.borrow())
.map_writer(move |writer| GetWrite {
index: key,
write: writer,
})
.into()
})
}
}
/// A specific index in a `Readable` / `Writable` hashmap
#[derive(Clone, Copy)]
pub struct GetWrite<Index, Write> {
index: Index,
write: Write,
}
impl<Index, Write, K, V, St> Readable for GetWrite<Index, Write>
where
Write: Readable<Target = HashMap<K, V, St>>,
Index: Hash + Eq + 'static,
K: Borrow<Index> + Eq + Hash + 'static,
St: BuildHasher + 'static,
{
type Target = V;
type Storage = Write::Storage;
fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
where
Self::Target: 'static,
{
self.write.try_read_unchecked().map(|value| {
Self::Storage::map(value, |value: &Write::Target| {
value
.get(&self.index)
.expect("Tried to access a key that does not exist")
})
})
}
fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
where
Self::Target: 'static,
{
self.write.try_peek_unchecked().map(|value| {
Self::Storage::map(value, |value: &Write::Target| {
value
.get(&self.index)
.expect("Tried to access a key that does not exist")
})
})
}
fn subscribers(&self) -> dioxus_core::Subscribers
where
Self::Target: 'static,
{
self.write.subscribers()
}
}
impl<Index, Write, K, V, St> Writable for GetWrite<Index, Write>
where
Write: Writable<Target = HashMap<K, V, St>>,
Index: Hash + Eq + 'static,
K: Borrow<Index> + Eq + Hash + 'static,
St: BuildHasher + 'static,
{
type WriteMetadata = Write::WriteMetadata;
fn try_write_unchecked(
&self,
) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>
where
Self::Target: 'static,
{
self.write.try_write_unchecked().map(|value| {
WriteLock::map(value, |value: &mut Write::Target| {
value
.get_mut(&self.index)
.expect("Tried to access a key that does not exist")
})
})
}
}
impl<Index, Write, K, V, St> ::std::convert::From<Store<V, GetWrite<Index, Write>>>
for Store<V, WriteSignal<V>>
where
Write::WriteMetadata: 'static,
Write: Writable<Target = HashMap<K, V, St>, Storage = UnsyncStorage> + 'static,
Index: Hash + Eq + 'static,
K: Borrow<Index> + Eq + Hash + 'static,
St: BuildHasher + 'static,
V: 'static,
{
fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
value
.into_selector()
.map_writer(|writer| WriteSignal::new(writer))
.into()
}
}
impl<Index, Write, K, V, St> ::std::convert::From<Store<V, GetWrite<Index, Write>>> for ReadStore<V>
where
Write: Readable<Target = HashMap<K, V, St>, Storage = UnsyncStorage> + 'static,
Index: Hash + Eq + 'static,
K: Borrow<Index> + Eq + Hash + 'static,
St: BuildHasher + 'static,
V: 'static,
{
fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
value
.into_selector()
.map_writer(|writer| ReadSignal::new(writer))
.into()
}
}
+136
View File
@@ -0,0 +1,136 @@
use std::{
hash::Hash,
ops::{self, Index, IndexMut},
};
use crate::{store::Store, ReadStore};
use dioxus_signals::{
AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, UnsyncStorage, Writable,
WriteLock, WriteSignal,
};
impl<Lens, T> Store<T, Lens> {
/// Index into the store, returning a store that allows access to the item at the given index. The
/// new store will only update when the item at the index changes.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| vec![1, 2, 3]);
/// let indexed_store = store.index(1);
/// // The indexed store can access the store methods of the indexed store.
/// assert_eq!(indexed_store(), 2);
/// ```
pub fn index<Idx>(self, index: Idx) -> Store<T::Output, IndexWrite<Idx, Lens>>
where
T: IndexMut<Idx> + 'static,
Idx: Hash + 'static,
Lens: Readable<Target = T> + 'static,
{
self.into_selector()
.hash_child_unmapped(&index)
.map_writer(move |write| IndexWrite { index, write })
.into()
}
}
/// A specific index in a `Readable` / `Writable` type
#[derive(Clone, Copy)]
pub struct IndexWrite<Index, Write> {
index: Index,
write: Write,
}
impl<Index, Write> Readable for IndexWrite<Index, Write>
where
Write: Readable,
Write::Target: ops::Index<Index> + 'static,
Index: Clone,
{
type Target = <Write::Target as ops::Index<Index>>::Output;
type Storage = Write::Storage;
fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
where
Self::Target: 'static,
{
self.write.try_read_unchecked().map(|value| {
Self::Storage::map(value, |value: &Write::Target| {
value.index(self.index.clone())
})
})
}
fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
where
Self::Target: 'static,
{
self.write.try_peek_unchecked().map(|value| {
Self::Storage::map(value, |value: &Write::Target| {
value.index(self.index.clone())
})
})
}
fn subscribers(&self) -> dioxus_core::Subscribers
where
Self::Target: 'static,
{
self.write.subscribers()
}
}
impl<Index, Write> Writable for IndexWrite<Index, Write>
where
Write: Writable,
Write::Target: ops::IndexMut<Index> + 'static,
Index: Clone,
{
type WriteMetadata = Write::WriteMetadata;
fn try_write_unchecked(
&self,
) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>
where
Self::Target: 'static,
{
self.write.try_write_unchecked().map(|value| {
WriteLock::map(value, |value: &mut Write::Target| {
value.index_mut(self.index.clone())
})
})
}
}
impl<Idx, T, Write> ::std::convert::From<Store<T, IndexWrite<Idx, Write>>>
for Store<T, WriteSignal<T>>
where
Write: Writable<Storage = UnsyncStorage> + 'static,
Write::WriteMetadata: 'static,
Write::Target: ops::IndexMut<Idx, Output = T> + 'static,
Idx: Clone + 'static,
T: 'static,
{
fn from(value: Store<T, IndexWrite<Idx, Write>>) -> Self {
value
.into_selector()
.map_writer(|writer| WriteSignal::new(writer))
.into()
}
}
impl<Idx, T, Write> ::std::convert::From<Store<T, IndexWrite<Idx, Write>>> for ReadStore<T>
where
Write: Readable<Storage = UnsyncStorage> + 'static,
Write::Target: ops::Index<Idx, Output = T> + 'static,
Idx: Clone + 'static,
T: 'static,
{
fn from(value: Store<T, IndexWrite<Idx, Write>>) -> Self {
value
.into_selector()
.map_writer(|writer| ReadSignal::new(writer))
.into()
}
}
+8
View File
@@ -0,0 +1,8 @@
mod btreemap;
mod deref;
mod hashmap;
mod index;
mod option;
mod result;
mod slice;
mod vec;
+230
View File
@@ -0,0 +1,230 @@
use std::ops::DerefMut;
use crate::{store::Store, MappedStore};
use dioxus_signals::{Readable, ReadableExt};
impl<Lens: Readable<Target = Option<T>> + 'static, T: 'static> Store<Option<T>, Lens> {
/// Checks if the `Option` is `Some`. This will only track the shallow state of the `Option`. It will
/// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// assert!(store.is_some());
/// ```
pub fn is_some(&self) -> bool {
self.selector().track_shallow();
self.selector().peek().is_some()
}
/// Returns true if the option is Some and the closure returns true. This will always track the shallow
/// state of the and will track the inner state of the enum if the enum is Some.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// assert!(store.is_some_and(|v| *v == 42));
/// ```
pub fn is_some_and(&self, f: impl FnOnce(&T) -> bool) -> bool {
self.selector().track_shallow();
let value = self.selector().peek();
if let Some(v) = &*value {
self.selector().track();
f(v)
} else {
false
}
}
/// Checks if the `Option` is `None`. This will only track the shallow state of the `Option`. It will
/// only cause a re-run if the `Option` could change from `Some` to `None` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| None::<i32>);
/// assert!(store.is_none());
/// ```
pub fn is_none(&self) -> bool {
self.selector().track_shallow();
self.selector().peek().is_none()
}
/// Returns true if the option is None or the closure returns true. This will always track the shallow
/// state of the and will track the inner state of the enum if the enum is Some.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// assert!(store.is_none_or(|v| *v == 42));
/// ```
pub fn is_none_or(&self, f: impl FnOnce(&T) -> bool) -> bool {
self.selector().track_shallow();
let value = self.selector().peek();
if let Some(v) = &*value {
self.selector().track();
f(v)
} else {
true
}
}
/// Transpose the `Store<Option<T>>` into a `Option<Store<T>>`. This will only track the shallow state of the `Option`. It will
/// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// let transposed = store.transpose();
/// match transposed {
/// Some(inner_store) => assert_eq!(inner_store(), 42),
/// None => panic!("Expected Some"),
/// }
/// ```
pub fn transpose(self) -> Option<MappedStore<T, Lens>> {
self.is_some().then(move || {
let map: fn(&Option<T>) -> &T = |value| {
value.as_ref().unwrap_or_else(|| {
panic!("Tried to access `Some` on an Option value");
})
};
let map_mut: fn(&mut Option<T>) -> &mut T = |value| {
value.as_mut().unwrap_or_else(|| {
panic!("Tried to access `Some` on an Option value");
})
};
self.into_selector().child(0, map, map_mut).into()
})
}
/// Unwraps the `Option` and returns a `Store<T>`. This will only track the shallow state of the `Option`. It will
/// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// let unwrapped = store.unwrap();
/// assert_eq!(unwrapped(), 42);
/// ```
pub fn unwrap(self) -> MappedStore<T, Lens> {
self.transpose().unwrap()
}
/// Expects the `Option` to be `Some` and returns a `Store<T>`. If the value is `None`, this will panic with `msg`. This will
/// only track the shallow state of the `Option`. It will only cause a re-run if the `Option` could change from `None`
/// to `Some` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// let unwrapped = store.expect("the answer to life the universe and everything");
/// assert_eq!(unwrapped(), 42);
/// ```
pub fn expect(self, msg: &str) -> MappedStore<T, Lens> {
self.transpose().expect(msg)
}
/// Returns a slice of the contained value, or an empty slice. This will not subscribe to any part of the store.
///
/// # Example
/// ```rust, no_run
/// use dioxus::prelude::*;
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// let slice = store.as_slice();
/// assert_eq!(&*slice.read(), [42]);
/// ```
pub fn as_slice(self) -> MappedStore<[T], Lens> {
let map: fn(&Option<T>) -> &[T] = |value| value.as_slice();
let map_mut: fn(&mut Option<T>) -> &mut [T] = |value| value.as_mut_slice();
self.into_selector().map(map, map_mut).into()
}
/// Transpose the store then coerce the contents of the Option with deref. This will only track the shallow state of the `Option`. It will
/// only cause a re-run if the `Option` could change from `None` to `Some` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(Box::new(42)));
/// let derefed = store.as_deref().unwrap();
/// assert_eq!(derefed(), 42);
/// ```
pub fn as_deref(self) -> Option<MappedStore<T::Target, Lens>>
where
T: DerefMut,
{
self.is_some().then(move || {
let map: fn(&Option<T>) -> &T::Target = |value| {
value
.as_ref()
.unwrap_or_else(|| {
panic!("Tried to access `Some` on an Option value");
})
.deref()
};
let map_mut: fn(&mut Option<T>) -> &mut T::Target = |value| {
value
.as_mut()
.unwrap_or_else(|| {
panic!("Tried to access `Some` on an Option value");
})
.deref_mut()
};
self.into_selector().child(0, map, map_mut).into()
})
}
/// Transpose the store then filter the contents of the Option with a closure. This will always track the shallow
/// state of the and will track the inner state of the enum if the enum is Some.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42));
/// let option = store.filter(|&v| v > 40);
/// let value = option.unwrap();
/// assert_eq!(value(), 42);
/// ```
pub fn filter(self, f: impl FnOnce(&T) -> bool) -> Option<MappedStore<T, Lens>> {
self.is_some_and(f).then(move || {
let map: fn(&Option<T>) -> &T = |value| {
value.as_ref().unwrap_or_else(|| {
panic!("Tried to access `Some` on an Option value");
})
};
let map_mut: fn(&mut Option<T>) -> &mut T = |value| {
value.as_mut().unwrap_or_else(|| {
panic!("Tried to access `Some` on an Option value");
})
};
self.into_selector().child(0, map, map_mut).into()
})
}
/// Call the function with a reference to the inner value if it is Some. This will always track the shallow
/// state of the and will track the inner state of the enum if the enum is Some.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Some(42)).inspect(|v| println!("{v}"));
/// ```
pub fn inspect(self, f: impl FnOnce(&T)) -> Self {
{
self.selector().track_shallow();
let value = self.selector().peek();
if let Some(v) = &*value {
self.selector().track();
f(v);
}
}
self
}
}
+336
View File
@@ -0,0 +1,336 @@
use std::fmt::Debug;
use crate::{store::Store, MappedStore};
use dioxus_signals::{Readable, ReadableExt, Writable};
impl<Lens, T, E> Store<Result<T, E>, Lens>
where
Lens: Readable<Target = Result<T, E>> + 'static,
T: 'static,
E: 'static,
{
/// Checks if the `Result` is `Ok`. This will only track the shallow state of the `Result`. It will
/// only cause a re-run if the `Result` could change from `Err` to `Ok` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<u32, ()>(42));
/// assert!(store.is_ok());
/// ```
pub fn is_ok(&self) -> bool {
self.selector().track_shallow();
self.selector().peek().is_ok()
}
/// Returns true if the result is Ok and the closure returns true. This will always track the shallow
/// state of the and will track the inner state of the enum if the enum is Ok.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<u32, ()>(42));
/// assert!(store.is_ok_and(|v| *v == 42));
/// ```
pub fn is_ok_and(&self, f: impl FnOnce(&T) -> bool) -> bool {
self.selector().track_shallow();
let value = self.selector().peek();
if let Ok(v) = &*value {
self.selector().track();
f(v)
} else {
false
}
}
/// Checks if the `Result` is `Err`. This will only track the shallow state of the `Result`. It will
/// only cause a re-run if the `Result` could change from `Ok` to `Err` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Err::<(), u32>(42));
/// assert!(store.is_err());
/// ```
pub fn is_err(&self) -> bool {
self.selector().track_shallow();
self.selector().peek().is_err()
}
/// Returns true if the result is Err and the closure returns true. This will always track the shallow
/// state of the and will track the inner state of the enum if the enum is Err.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Err::<(), u32>(42));
/// assert!(store.is_err_and(|v| *v == 42));
/// ```
pub fn is_err_and(&self, f: impl FnOnce(&E) -> bool) -> bool {
self.selector().track_shallow();
let value = self.selector().peek();
if let Err(e) = &*value {
self.selector().track();
f(e)
} else {
false
}
}
/// Converts `Store<Result<T, E>>` into `Option<Store<T>>`, discarding the error if present. This will
/// only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could
/// change from `Err` to `Ok` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<u32, ()>(42));
/// match store.ok() {
/// Some(ok_store) => assert_eq!(ok_store(), 42),
/// None => panic!("Expected Ok"),
/// }
/// ```
pub fn ok(self) -> Option<MappedStore<T, Lens>> {
let map: fn(&Result<T, E>) -> &T = |value| {
value.as_ref().unwrap_or_else(|_| {
panic!("Tried to access `ok` on an Err value");
})
};
let map_mut: fn(&mut Result<T, E>) -> &mut T = |value| {
value.as_mut().unwrap_or_else(|_| {
panic!("Tried to access `ok` on an Err value");
})
};
self.is_ok()
.then(|| self.into_selector().child(0, map, map_mut).into())
}
/// Converts `Store<Result<T, E>>` into `Option<Store<E>>`, discarding the success if present. This will
/// only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could
/// change from `Ok` to `Err` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Err::<(), u32>(42));
/// match store.err() {
/// Some(err_store) => assert_eq!(err_store(), 42),
/// None => panic!("Expected Err"),
/// }
/// ```
pub fn err(self) -> Option<MappedStore<E, Lens>>
where
Lens: Writable<Target = Result<T, E>> + 'static,
{
self.is_err().then(|| {
let map: fn(&Result<T, E>) -> &E = |value| match value {
Ok(_) => panic!("Tried to access `err` on an Ok value"),
Err(e) => e,
};
let map_mut: fn(&mut Result<T, E>) -> &mut E = |value| match value {
Ok(_) => panic!("Tried to access `err` on an Ok value"),
Err(e) => e,
};
self.into_selector().child(1, map, map_mut).into()
})
}
/// Transposes the `Store<Result<T, E>>` into a `Result<Store<T>, Store<E>>`. This will only track the
/// shallow state of the `Result`. It will only cause a re-run if the `Result` could change from `Err` to
/// `Ok` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<u32, ()>(42));
/// match store.transpose() {
/// Ok(ok_store) => assert_eq!(ok_store(), 42),
/// Err(err_store) => assert_eq!(err_store(), ()),
/// }
/// ```
#[allow(clippy::result_large_err)]
pub fn transpose(self) -> Result<MappedStore<T, Lens>, MappedStore<E, Lens>>
where
Lens: Writable<Target = Result<T, E>> + 'static,
{
if self.is_ok() {
let map: fn(&Result<T, E>) -> &T = |value| match value {
Ok(t) => t,
Err(_) => panic!("Tried to access `ok` on an Err value"),
};
let map_mut: fn(&mut Result<T, E>) -> &mut T = |value| match value {
Ok(t) => t,
Err(_) => panic!("Tried to access `ok` on an Err value"),
};
Ok(self.into_selector().child(0, map, map_mut).into())
} else {
let map: fn(&Result<T, E>) -> &E = |value| match value {
Ok(_) => panic!("Tried to access `err` on an Ok value"),
Err(e) => e,
};
let map_mut: fn(&mut Result<T, E>) -> &mut E = |value| match value {
Ok(_) => panic!("Tried to access `err` on an Ok value"),
Err(e) => e,
};
Err(self.into_selector().child(1, map, map_mut).into())
}
}
/// Unwraps the `Result` and returns a `Store<T>`. This will only track the shallow state of the `Result`.
/// It will only cause a re-run if the `Result` could change from `Err` to `Ok` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<u32, ()>(42));
/// let unwrapped = store.unwrap();
/// assert_eq!(unwrapped(), 42);
/// ```
pub fn unwrap(self) -> MappedStore<T, Lens>
where
Lens: Writable<Target = Result<T, E>> + 'static,
E: Debug,
{
self.transpose().unwrap()
}
/// Expects the `Result` to be `Ok` and returns a `Store<T>`. If the value is `Err`, this will panic with `msg`.
/// This will only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could
/// change from `Err` to `Ok` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<u32, ()>(42));
/// let unwrapped = store.expect("Expected Ok");
/// assert_eq!(unwrapped(), 42);
/// ```
pub fn expect(self, msg: &str) -> MappedStore<T, Lens>
where
Lens: Writable<Target = Result<T, E>> + 'static,
E: Debug,
{
self.transpose().expect(msg)
}
/// Unwraps the error variant of the `Result`. This will only track the shallow state of the `Result`.
/// It will only cause a re-run if the `Result` could change from `Ok` to `Err` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Err::<(), u32>(42));
/// let unwrapped_err = store.unwrap_err();
/// assert_eq!(unwrapped_err(), 42);
/// ```
pub fn unwrap_err(self) -> MappedStore<E, Lens>
where
Lens: Writable<Target = Result<T, E>> + 'static,
T: Debug,
{
self.transpose().unwrap_err()
}
/// Expects the `Result` to be `Err` and returns a `Store<E>`. If the value is `Ok`, this will panic with `msg`.
/// This will only track the shallow state of the `Result`. It will only cause a re-run if the `Result` could
/// change from `Ok` to `Err` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Err::<(), u32>(42));
/// let unwrapped_err = store.expect_err("Expected Err");
/// assert_eq!(unwrapped_err(), 42);
/// ```
pub fn expect_err(self, msg: &str) -> MappedStore<E, Lens>
where
Lens: Writable<Target = Result<T, E>> + 'static,
T: Debug,
{
self.transpose().expect_err(msg)
}
/// Call the function with a reference to the inner value if it is Ok. This will always track the shallow
/// state of the and will track the inner state of the enum if the enum is Ok.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<u32, ()>(42)).inspect(|v| println!("{v}"));
/// ```
pub fn inspect(self, f: impl FnOnce(&T)) -> Self
where
Lens: Writable<Target = Result<T, E>> + 'static,
{
{
self.selector().track_shallow();
let value = self.selector().peek();
if let Ok(value) = &*value {
self.selector().track();
f(value);
}
}
self
}
/// Call the function with a mutable reference to the inner value if it is Err. This will always track the shallow
/// state of the `Result` and will track the inner state of the enum if the enum is Err.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Err::<(), u32>(42)).inspect_err(|v| println!("{v}"));
/// ```
pub fn inspect_err(self, f: impl FnOnce(&E)) -> Self
where
Lens: Writable<Target = Result<T, E>> + 'static,
{
{
self.selector().track_shallow();
let value = self.selector().peek();
if let Err(value) = &*value {
self.selector().track();
f(value);
}
}
self
}
/// Transpose the store then coerce the contents of the Result with deref. This will only track the shallow state of the `Result`. It will
/// only cause a re-run if the `Result` could change from `Err` to `Ok` or vice versa.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| Ok::<Box<u32>, ()>(Box::new(42)));
/// let derefed = store.as_deref().unwrap();
/// assert_eq!(derefed(), 42);
/// ```
pub fn as_deref(self) -> Result<MappedStore<T::Target, Lens>, MappedStore<E, Lens>>
where
Lens: Writable<Target = Result<T, E>> + 'static,
T: std::ops::DerefMut,
{
if self.is_ok() {
let map: fn(&Result<T, E>) -> &T::Target = |value| match value {
Ok(t) => t.deref(),
Err(_) => panic!("Tried to access `ok` on an Err value"),
};
let map_mut: fn(&mut Result<T, E>) -> &mut T::Target = |value| match value {
Ok(t) => t.deref_mut(),
Err(_) => panic!("Tried to access `ok` on an Err value"),
};
Ok(self.into_selector().child(0, map, map_mut).into())
} else {
let map: fn(&Result<T, E>) -> &E = |value| match value {
Ok(_) => panic!("Tried to access `err` on an Ok value"),
Err(e) => e,
};
let map_mut: fn(&mut Result<T, E>) -> &mut E = |value| match value {
Ok(_) => panic!("Tried to access `err` on an Ok value"),
Err(e) => e,
};
Err(self.into_selector().child(1, map, map_mut).into())
}
}
}
+60
View File
@@ -0,0 +1,60 @@
use std::iter::FusedIterator;
use crate::{impls::index::IndexWrite, store::Store};
use dioxus_signals::{Readable, ReadableExt};
impl<Lens, I> Store<Vec<I>, Lens>
where
Lens: Readable<Target = Vec<I>> + 'static,
I: 'static,
{
/// Returns the length of the slice. This will only track the shallow state of the slice.
/// It will only cause a re-run if the length of the slice could change.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| vec![1, 2, 3]);
/// assert_eq!(store.len(), 3);
/// ```
pub fn len(&self) -> usize {
self.selector().track_shallow();
self.selector().peek().len()
}
/// Checks if the slice is empty. This will only track the shallow state of the slice.
/// It will only cause a re-run if the length of the slice could change.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| vec![1, 2, 3]);
/// assert!(!store.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.selector().track_shallow();
self.selector().peek().is_empty()
}
/// Returns an iterator over the items in the slice. This will only track the shallow state of the slice.
/// It will only cause a re-run if the length of the slice could change.
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let store = use_store(|| vec![1, 2, 3]);
/// for item in store.iter() {
/// println!("{}", item);
/// }
/// ```
pub fn iter(
&self,
) -> impl ExactSizeIterator<Item = Store<I, IndexWrite<usize, Lens>>>
+ DoubleEndedIterator
+ FusedIterator
+ '_
where
Lens: Clone,
{
(0..self.len()).map(move |i| self.clone().index(i))
}
}
+88
View File
@@ -0,0 +1,88 @@
use crate::store::Store;
use dioxus_signals::Writable;
impl<Lens: Writable<Target = Vec<T>> + 'static, T: 'static> Store<Vec<T>, Lens> {
/// Pushes an item to the end of the vector. This will only mark the length of the vector as dirty.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let mut store = use_store(|| vec![1, 2, 3]);
/// store.push(4);
/// ```
pub fn push(&mut self, value: T) {
self.selector().mark_dirty_shallow();
self.selector().write_untracked().push(value);
}
/// Removes an item from the vector at the specified index and returns it. This will mark items after
/// the index and the length of the vector as dirty.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let mut store = use_store(|| vec![1, 2, 3]);
/// let removed = store.remove(1);
/// assert_eq!(removed, 2);
/// ```
pub fn remove(&mut self, index: usize) -> T {
self.selector().mark_dirty_shallow();
self.selector().mark_dirty_at_and_after_index(index);
self.selector().write_untracked().remove(index)
}
/// Inserts an item at the specified index in the vector. This will mark items at and after the index
/// and the length of the vector as dirty.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let mut store = use_store(|| vec![1, 2, 3]);
/// store.insert(1, 4);
/// ```
pub fn insert(&mut self, index: usize, value: T) {
self.selector().mark_dirty_shallow();
self.selector().mark_dirty_at_and_after_index(index);
self.selector().write_untracked().insert(index, value);
}
/// Clears the vector, marking it as dirty.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let mut store = use_store(|| vec![1, 2, 3]);
/// store.clear();
/// ```
pub fn clear(&mut self) {
self.selector().mark_dirty();
self.selector().write_untracked().clear();
}
/// Retains only the elements specified by the predicate. This will only mark the length of the vector
/// and items after the first removed item as dirty.
///
/// # Example
/// ```rust, no_run
/// use dioxus_stores::*;
/// let mut store = use_store(|| vec![1, 2, 3, 4, 5]);
/// store.retain(|&x| x % 2 == 0);
/// assert_eq!(store.len(), 2);
/// ```
pub fn retain(&mut self, mut f: impl FnMut(&T) -> bool) {
let mut index = 0;
let mut first_removed_index = None;
self.selector().write_untracked().retain(|item| {
let keep = f(item);
if !keep {
first_removed_index = first_removed_index.or(Some(index));
}
index += 1;
keep
});
if let Some(index) = first_removed_index {
self.selector().mark_dirty_shallow();
self.selector().mark_dirty_at_and_after_index(index);
}
}
}