Leptos Query - a robust asynchronous state management library for Leptos,

Overview

Leptos Query

Crates.io docs.rs

About

Leptos Query is a robust asynchronous state management library for Leptos, providing simplified data fetching, integrated reactivity, server-side rendering support, and intelligent cache management.

Heavily inspired by Tanstack Query.

Why Choose Leptos Query?

Leptos Query focuses on simplifying your data fetching process and keeping your application's state effortlessly synchronized and up-to-date. Here's how it's done:

Key Features

  • Configurable Caching & SWR: Queries are cached by default, ensuring quick access to your data. You can configure your stale and cache times per query with Stale While Revalidate (SWR) system.

  • Reactivity at Its Core: Leptos Query deeply integrates with Leptos' reactive system to transform asynchronous query fetchers into reactive Signals.

  • Server-Side Rendering (SSR) Compatibility: Fetch your queries on the server and smoothly serialize them to the client, just as you would with a Leptos Resource.

  • Efficient De-duplication: No unnecessary fetches here! If you make multiple queries with the same Key, Leptos Query smartly fetches only once.

  • Manual Invalidation: Control when your queries should be invalidated and refetched for that ultimate flexibility.

  • Scheduled Refetching: Set up your queries to refetch on a customized schedule, keeping your data fresh as per your needs.

Installation

cargo add leptos_query --optional

Then add the relevant feature(s) to your Cargo.toml

[features]
hydrate = [
    "leptos_query/hydrate",
    # ...
]
ssr = [
    "leptos_query/ssr",
    # ...
 ]

Quick Start

In the root of your App, provide a query client:

use leptos_query::*;
use leptos::*;

#[component]
pub fn App(cx: Scope) -> impl IntoView {
    // Provides Query Client for entire app.
    provide_query_client(cx);

    // Rest of App...
}

Then make a query function.

NOTE:

  • A query is unique per Key K.
  • A query Key type K must only correspond to ONE UNIQUE Value V Type.
    • Meaning a query Key type K cannot correspond to multiple V Types.

TLDR: Wrap your key in a Newtype when needed to ensure uniqueness.

 use leptos::*;
 use leptos_query::*;
 use std::time::Duration;
 use serde::*;

 // Data type.
 #[derive(Clone, Deserialize, Serialize)]
 struct Monkey {
     name: String,
 }

 // Create a Newtype for MonkeyId.
 #[derive(Clone, PartialEq, Eq, Hash)]
 struct MonkeyId(String);


 // Monkey fetcher.
 async fn get_monkey(id: MonkeyId) -> Monkey {
    todo!()
 }

 // Query for a Monkey.
 fn use_monkey_query(cx: Scope, id: impl Fn() -> MonkeyId + 'static) -> QueryResult<Monkey> {
     leptos_query::use_query(
         cx,
         id,
         get_monkey,
         QueryOptions {
             default_value: None,
             refetch_interval: None,
             resource_option: ResourceOption::NonBlocking,
             // Considered stale after 5 seconds.
             stale_time: Some(Duration::from_secs(5)),
             // Infinite cache time.
             cache_time: None,
         },
     )
 }

Now you can use the query in any component in your app.

{move || { if is_loading.get() { "Loading..." } else { "Loaded" } }}
"Fetching Status: " {move || { if is_fetching.get() { "Fetching..." } else { "Idle" } }}
"Stale Status: " {move || { if is_stale.get() { "Stale" } else { "Fresh" } }}
// Query data should be read inside a Transition/Suspense component. "Loading..." } }> {move || { data.get() .map(|monkey| { view! { cx,

{monkey.name}

} }) }}
} } ">
#[component]
fn MonkeyView(cx: Scope, id: MonkeyId) -> impl IntoView {
    let query = use_monkey_query(cx, move || id.clone());
    let QueryResult {
        data,
        is_loading,
        is_fetching,
        is_stale
        ..
    } = query;

    view! { cx,
      // You can use the query result data here.
      // Everything is reactive.
       
"Loading Status: " {move || { if is_loading.get() { "Loading..." } else { "Loaded" } }}
"Fetching Status: " {move || { if is_fetching.get() { "Fetching..." } else { "Idle" } }}
"Stale Status: " {move || { if is_stale.get() { "Stale" } else { "Fresh" } }}
// Query data should be read inside a Transition/Suspense component. <Transition fallback=move || { view! { cx,

"Loading..."

} }> {move || { data.get() .map(|monkey| { view! { cx,

{monkey.name}

} }) }} Transition>
} }

For a complete working example see the example directory

FAQ

How's this different from a leptos Resource?

A Query uses a resource under the hood, but provides additional functionality like caching, de-duplication, and invalidation.

Resources are individually bound to the Scope they are created in. Queries are all bound to the QueryClient they are created in. Meaning, once you have a QueryClient in your app, you can access the value for a query anywhere in your app.

With a resource, you have to manually lift it to a higher scope if you want to preserve it. And this can be cumbersome if you have a many resources.

Also, queries are stateful on a per-key basis, meaning you can use the same query with for the same key in multiple places and only one request will be made, and they all share the same state.

What's the difference between stale_time and cache_time?

staleTime is the duration until a query transitions from fresh to stale. As long as the query is fresh, data will always be read from the cache only.

When a query is stale, it will be refetched on its next usage.

cacheTime is the duration until inactive queries will be removed from cache.

  • Default value for stale_time is 0 seconds.
  • Default value for cache_time is 5 minutes.

These can be configured per-query using QueryOptions

If you want infinite cache/stale time, you can set stale_time and cache_time to None.

NOTE: stale_time can never be greater than cache_time. If stale_time is greater than cache_time, stale_time will be set to cache_time.

What's a QueryClient?

A QueryClient allows you to interact with the query cache. You can invalidate queries, prefetch them, and introspect the query cache.

use_query_client() will return the QueryClient for the current scope.

What's invalidating a query do?

Sometimes you can't wait for a query to become stale before you refetch it. QueryClient has an invalidate_query method that lets you intelligently mark queries as stale and potentially refetch them too!

When a query is invalidated, the following happens:

  • It is marked as invalid. This invalid state overrides any stale_time configuration.
  • The next time the query is used, it will be refetched in the background.
    • If a query is currently being used, it will be refetched immediately.

What's the difference between is_loading and is_fetching?

is_fetching is true when the query is in the process of fetching data.

is_loading is true when the query is in the process of fetching data FOR THE FIRST TIME.

You might also like...
EmbedAnything is a powerful python library designed to streamline the creation and management of embedding pipelines
EmbedAnything is a powerful python library designed to streamline the creation and management of embedding pipelines

EmbedAnything is a powerful python library designed to streamline the creation and management of embedding pipelines. Built in Rust with no heavy dependencies.

A simple solution for scoped styles in Leptos

Styled: Easy Styling for Leptos Components If you're looking for an easy way to apply scoped styles to your Leptos components, Styled is the Leptos ma

A formatter for the leptos view! macro

leptosfmt A formatter for the leptos view! macro All notable changes are documented in: CHANGELOG.md Install cargo install leptosfmt or for trying out

A website for the Leptos Web Framework!

Leptos Website A Repo for the public facing Leptos website. Still a WIP. Client Side Rendering This example cannot be built as a trunk standalone CSR-

A Nix template for full-stack web apps in Rust using Leptos

leptos-fullstack A Nix template for full-stack web apps in Rust using Leptos. Tech used: Leptos full-stack framework server functions ssr + hydration

A zero-config leptos component to display markdown
A zero-config leptos component to display markdown

A port of yew-markdown using leptos ! Usage You can use this component to render both static and dynamic markdown. Static markdown use leptos::*; {

Experimental integration of `fedimint-client` with the Leptos web frontend framework
Experimental integration of `fedimint-client` with the Leptos web frontend framework

CAUTION: highly experimental, the Database implementation is likely horribly broken Fedimint Client built with Leptos This repo contains a proof-of-co

Leptos server signals synced through Server-Sent-Events (SSE)

Leptos Server Sent Events Server signals are leptos signals kept in sync with the server through server-sent-events (SSE). The signals are read-only o

Leptos integration with Golden Layout.

Leptos Golden Layout Leptos integration with Golden Layout. This has many features missing but covers the basics for integrating leptos with golden la

Comments
  • Reactive Key for use_query

    Reactive Key for use_query

    use_query now works with a reactive key.

    Many ideas were used from Tanstack Query. The main takeaway that I got from reading source code from tanstack/query is the Resource created in use_query should not execute the provided async query function.

    This makes it impossible to de-duplicate the same query in different parts of the application, which is a killer feature.

    Instead the Resource should simply synchronize with the query state.

    The resource should suspend (async timeout never resolving) if the query is loading. if it's not loading then just get the latest value.

    Then in an effect, interrupt the resource if it is loading by setting the resource directly (SignalSet).

    So the initial resource resolution works like this:

    resource reads state, state is not present resource is suspended executor executes the query using spawn_local executor then sets the state of the query executor then calls resource.refetch resource reads the new state


    NOTES

    The only known quirk with the current implementation is the following:

    When the key changes and the data for the new key hasn't been fetched yet, the fallback view in a <Transition/> is not re-triggered. Meaning you will see the old data for the previous key until the new data comes in.

    opened by nicoburniske 0
Owner
Nico Burniske
Functional programmer mostly. Bitcoiner. Yogi. Jobava London enthusiast. Analog photographer. Ultimate handler. Barefoot runner. Mustache sporter.
Nico Burniske
global state management for dioxus built on the concept of atoms. currently under 🏗

Fermi: A global state management solution for Dioxus, inspired by Recoil.Js Fermi provides primitives for managing global state in Dioxus applications

Dioxus 15 Feb 12, 2022
Fully-typed, async, reusable state management and synchronization for Dioxus 🧬

dioxus-query ?? ⚡ Fully-typed, async, reusable state management and synchronization for Dioxus ??. Inspired by TanStack Query. ⚠️ Work in progress ⚠️

Marc Espín 9 Aug 7, 2023
Fully-typed global state management with a topics subscription system for Dioxus 🧬

dioxus-radio ???? ⚠️ Work in progress !! ⚠️ . Fully-typed global state management with a topics subscription system for Dioxus ??. Who is this for You

Dioxus Community 6 Dec 14, 2023
This is a `Rust` based package to help with the management of complex medicine (pill) management cycles.

reepicheep This is a Rust based package to help with the management of complex medicine (pill) management cycles. reepicheep notifies a person(s) via

Daniel B 24 Dec 13, 2023
Rust File Management CLI is a command-line tool written in Rust that provides essential file management functionalities. Whether you're working with files or directories, this tool simplifies common file operations with ease.

Rust FileOps Rust File Management CLI is a command-line tool written in Rust that provides essential file management functionalities. Whether you're w

Harikesh Ranjan Sinha 5 May 2, 2024
💫 Easy to use, robust Rust library for displaying spinners in the terminal

spinoff an easy to use, robust library for displaying spinners in the terminal ?? Install Add as a dependency to your Cargo.toml: [dependencies] spino

ad4m 401 Jun 24, 2023
A robust, customizable, blazingly-fast, efficient and easy-to-use command line application to uwu'ify your text!

uwuifyy A robust, customizable, blazingly-fast, efficient and easy-to-use command line application to uwu'ify your text! Logo Credits: Jade Nelson Tab

Hamothy 43 Dec 12, 2022
A fast and robust MLOps tool for managing data and pipelines

xvc A Fast and Robust MLOps Swiss-Army Knife in Rust ⌛ When to use xvc? Machine Learning Engineers: When you manage large quantities of unstructured d

Emre Sahin 6 Dec 15, 2022
Experimental language build in Rust to make it fast and robust

Reg-lang Experimental language build with Rust. Its aim is : To be simple to help learning programmation with, and in a second hand, to be robust enou

Gipson62 1 Dec 29, 2022
A UI component library for Leptos, based on Tailwind Elements.

leptos-twelements A UI component library for Leptos, based on Tailwind Elements. Installation (for projects using cargo leptos) Use the nightly rust c

Sebastian Meßmer 4 Oct 20, 2023