react-rs-core 0.3.0

Core reactive primitives for react.rs - Signal, Effect, Memo, Context
Documentation
use crate::signal::{create_signal, ReadSignal, WriteSignal};

#[derive(Debug, Clone, PartialEq)]
pub enum ResourceState<T> {
    Loading,
    Ready(T),
    Error(String),
}

impl<T> ResourceState<T> {
    pub fn is_loading(&self) -> bool {
        matches!(self, ResourceState::Loading)
    }

    pub fn is_ready(&self) -> bool {
        matches!(self, ResourceState::Ready(_))
    }

    pub fn is_error(&self) -> bool {
        matches!(self, ResourceState::Error(_))
    }

    pub fn data(&self) -> Option<&T> {
        match self {
            ResourceState::Ready(d) => Some(d),
            _ => None,
        }
    }

    pub fn error(&self) -> Option<&str> {
        match self {
            ResourceState::Error(e) => Some(e),
            _ => None,
        }
    }
}

pub struct Resource<T: Clone + 'static> {
    state: ReadSignal<ResourceState<T>>,
    set_state: WriteSignal<ResourceState<T>>,
}

impl<T: Clone + 'static> Resource<T> {
    pub fn state(&self) -> ReadSignal<ResourceState<T>> {
        self.state.clone()
    }

    pub fn read(&self) -> ResourceState<T> {
        self.state.get()
    }

    pub fn loading(&self) -> bool {
        self.state.get().is_loading()
    }

    pub fn data(&self) -> Option<T> {
        match self.state.get() {
            ResourceState::Ready(d) => Some(d),
            _ => None,
        }
    }

    pub fn set_ready(&self, data: T) {
        self.set_state.set(ResourceState::Ready(data));
    }

    pub fn set_error(&self, error: impl Into<String>) {
        self.set_state.set(ResourceState::Error(error.into()));
    }

    pub fn set_loading(&self) {
        self.set_state.set(ResourceState::Loading);
    }
}

impl<T: Clone + 'static> Clone for Resource<T> {
    fn clone(&self) -> Self {
        Self {
            state: self.state.clone(),
            set_state: self.set_state.clone(),
        }
    }
}

pub fn create_resource<T: Clone + 'static>() -> Resource<T> {
    let (state, set_state) = create_signal(ResourceState::Loading);
    Resource { state, set_state }
}

pub fn create_resource_with<T: Clone + 'static>(initial: T) -> Resource<T> {
    let (state, set_state) = create_signal(ResourceState::Ready(initial));
    Resource { state, set_state }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_resource_initial_loading() {
        let resource = create_resource::<String>();
        assert!(resource.loading());
        assert!(resource.data().is_none());
    }

    #[test]
    fn test_resource_set_ready() {
        let resource = create_resource::<String>();
        resource.set_ready("hello".to_string());
        assert!(!resource.loading());
        assert_eq!(resource.data(), Some("hello".to_string()));
    }

    #[test]
    fn test_resource_set_error() {
        let resource = create_resource::<String>();
        resource.set_error("network error");
        assert!(resource.read().is_error());
        assert_eq!(resource.read().error(), Some("network error"));
    }

    #[test]
    fn test_resource_with_initial() {
        let resource = create_resource_with(42);
        assert!(!resource.loading());
        assert_eq!(resource.data(), Some(42));
    }

    #[test]
    fn test_resource_state_transitions() {
        let resource = create_resource::<Vec<String>>();
        assert!(resource.loading());

        resource.set_ready(vec!["a".to_string()]);
        assert_eq!(resource.data().unwrap().len(), 1);

        resource.set_loading();
        assert!(resource.loading());

        resource.set_error("timeout");
        assert!(resource.read().is_error());
    }
}