Skip to content

Commit dc8fd37

Browse files
authored
docs: add create_resource, <Suspense/>, and <Transition/> (leptos-rs#559)
1 parent cc2b310 commit dc8fd37

File tree

6 files changed

+149
-4
lines changed

6 files changed

+149
-4
lines changed

docs/book/src/SUMMARY.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
- [Passing Children to Components](./view/09_component_children.md)
1515
- [Interlude: Reactivity and Functions](./interlude_functions.md)
1616
- [Testing](./testing.md)
17-
- [Interlude: Styling — CSS, Tailwind, Style.rs, and more]()
18-
- [Async]()
19-
- [Resource]()
20-
- [Suspense]()
17+
- [Async](./async/README.md)
18+
- [Loading Data with Resources](./async/10_resources.md)
19+
- [Suspense](./async/11_suspense.md)
2120
- [Transition]()
21+
- [Interlude: Styling — CSS, Tailwind, Style.rs, and more]()
2222
- [State Management]()
2323
- [Interlude: Advanced Reactivity]()
2424
- [Router]()

docs/book/src/async/10_resources.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Loading Data with Resources
2+
3+
A [Resource](https://docs.rs/leptos/latest/leptos/struct.Resource.html) is a reactive data structure that reflects the current state of an asynchronous task, allowing you to integrate asynchronous `Future`s into the synchronous reactive system. Rather than waiting for its data to load with `.await`, you transform the `Future` into a signal that returns `Some(T)` if it has resolved, and `None` if it’s still pending.
4+
5+
You do this by using the [`create_resource`](https://docs.rs/leptos/latest/leptos/fn.create_resource.html) function. This takes two arguments (other than the ubiquitous `cx`):
6+
7+
1. a source signal, which will generate a new `Future` whenever it changes
8+
2. a fetcher function, which takes the data from that signal and returns a `Future`
9+
10+
Here’s an example
11+
12+
```rust
13+
// our source signal: some synchronous, local state
14+
let (count, set_count) = create_signal(cx, 0);
15+
16+
// our resource
17+
let async_data = create_resource(cx,
18+
count,
19+
// every time `count` changes, this will run
20+
|value| async move {
21+
log!("loading data from API");
22+
load_data(value).await
23+
},
24+
);
25+
```
26+
27+
To create a resource that simply runs once, you can pass a non-reactive, empty source signal:
28+
29+
```rust
30+
let once = create_resource(cx, || (), |_| async move { load_data().await });
31+
```
32+
33+
To access the value you can use `.read(cx)` or `.with(cx, |data| /* */)`. These work just like `.get()` and `.with()` on a signal—`read` clones the value and returns it, `with` applies a closure to it—but with two differences
34+
35+
1. For any `Resource<_, T>`, they always return `Option<T>`, not `T`: because it’s always possible that your resource is still loading.
36+
2. They take a `Scope` argument. You’ll see why in the next chapter, on `<Suspense/>`.
37+
38+
So, you can show the current state of a resource in your view:
39+
40+
```rust
41+
let once = create_resource(cx, || (), |_| async move { load_data().await });
42+
view! { cx,
43+
<h1>"My Data"</h1>
44+
{move || match once.read(cx) {
45+
None => view! { cx, <p>"Loading..."</p> }.into_view(cx),
46+
Some(data) => view! { cx, <ShowData data/> }.into_view(cx)
47+
}}
48+
}
49+
```
50+
51+
Resources also provide a `refetch()` method that allow you to manually reload the data (for example, in response to a button click) and a `loading()` method that returns a `ReadSignal<bool>` indicating whether the resource is currently loading or not.
52+
53+
<iframe src="https://codesandbox.io/p/sandbox/10-async-resources-4z0qt3?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A3%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A3%7D%5D" width="100%" height="1000px"></iframe>

docs/book/src/async/11_suspense.md

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# `<Suspense/>`
2+
3+
In the previous chapter, we showed how you can create a simple loading screen to show some fallback while a resource is loading.
4+
5+
```rust
6+
let (count, set_count) = create_signal(cx, 0);
7+
let a = create_resource(cx, count, |count| async move { load_a(count).await });
8+
9+
view! { cx,
10+
<h1>"My Data"</h1>
11+
{move || match once.read(cx) {
12+
None => view! { cx, <p>"Loading..."</p> }.into_view(cx),
13+
Some(data) => view! { cx, <ShowData data/> }.into_view(cx)
14+
}}
15+
}
16+
```
17+
18+
But what if we have two resources, and want to wait for both of them?
19+
20+
```rust
21+
let (count, set_count) = create_signal(cx, 0);
22+
let (count2, set_count2) = create_signal(cx, 0);
23+
let a = create_resource(cx, count, |count| async move { load_a(count).await });
24+
let b = create_resource(cx, count2, |count| async move { load_b(count).await });
25+
26+
view! { cx,
27+
<h1>"My Data"</h1>
28+
{move || match (a.read(cx), b.read(cx)) {
29+
_ => view! { cx, <p>"Loading..."</p> }.into_view(cx),
30+
(Some(a), Some(b)) => view! { cx,
31+
<ShowA a/>
32+
<ShowA b/>
33+
}.into_view(cx)
34+
}}
35+
}
36+
```
37+
38+
That’s not _so_ bad, but it’s kind of annoying. What if we could invert the flow of control?
39+
40+
The [`<Suspense/>`](https://docs.rs/leptos/latest/leptos/fn.Suspense.html) component lets us do exactly that. You give it a `fallback` prop and children, one or more of which usually involves reading from a resource. Reading from a resource “under” a `<Suspense/>` (i.e., in one of its children) registers that resource with the `<Suspense/>`. If it’s still waiting for resources to load, it shows the `fallback`. When they’ve all loaded, it shows the children.
41+
42+
```rust
43+
let (count, set_count) = create_signal(cx, 0);
44+
let (count2, set_count2) = create_signal(cx, 0);
45+
let a = create_resource(cx, count, |count| async move { load_a(count).await });
46+
let b = create_resource(cx, count2, |count| async move { load_b(count).await });
47+
48+
view! { cx,
49+
<h1>"My Data"</h1>
50+
<Suspense
51+
fallback=move || view! { cx, <p>"Loading..."</p> }
52+
>
53+
<h2>"My Data"</h2>
54+
<h3>"A"</h3>
55+
{move || {
56+
a.read(cx)
57+
.map(|a| view! { cx, <ShowA a/> })
58+
}}
59+
<h3>"B"</h3>
60+
{move || {
61+
b.read(cx)
62+
.map(|b| view! { cx, <ShowB b/> })
63+
}}
64+
</Suspense>
65+
}
66+
```
67+
68+
Every time one of the resources is reloading, the `"Loading..."` fallback will show again.
69+
70+
This inversion of the flow of control makes it easier to add or remove individual resources, as you don’t need to handle the matching yourself. It also unlocks some massive performance improvements during server-side rendering, which we’ll talk about during a later chapter.
71+
72+
<iframe src="https://codesandbox.io/p/sandbox/10-async-resources-4z0qt3?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A3%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A3%7D%5D" width="100%" height="1000px"></iframe>

docs/book/src/async/12_transition.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# `<Transition/>`
2+
3+
You’ll notice in the `<Suspense/>` example that if you keep reloading the data, it keeps flickering back to `"Loading..."`. Sometimes this is fine. For other times, there’s [`<Transition/>`](https://docs.rs/leptos/latest/leptos/fn.Suspense.html).
4+
5+
`<Transition/>` behaves exactly the same as `<Suspense/>`, but instead of falling back every time, it only shows the fallback the first time. On all subsequent loads, it continues showing the old data until the new data are ready. This can be really handy to prevent the flickering effect, and to allow users to continue interacting with your application.
6+
7+
This example shows how you can create a simple tabbed contact list with `<Transition/>`. When you select a new tab, it continues showing the current contact until the new data laods. This can be a much better user experience than constantly falling back to a loading message.
8+
9+
<iframe src="https://codesandbox.io/p/sandbox/12-transition-sn38sd?selection=%5B%7B%22endColumn%22%3A15%2C%22endLineNumber%22%3A2%2C%22startColumn%22%3A15%2C%22startLineNumber%22%3A2%7D%5D&file=%2Fsrc%2Fmain.rs" width="100%" height="1000px"></iframe>

docs/book/src/async/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Working with `async`
2+
3+
So far we’ve only been working with synchronous users interfaces: You provide some input,
4+
the app immediately process it and updates the interface. This is great, but is a tiny
5+
subset of what web applications do. In particular, most web apps have to deal with some kind
6+
of asynchronous data loading, usually loading something from an API.
7+
8+
Asynchronous data is notoriously hard to integrate with the synchronous parts of your code.
9+
In this chapter, we’ll see how Leptos helps smooth out that process for you.

docs/book/src/view/09_component_children.md

+2
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,5 @@ view! { cx,
122122
</WrappedChildren>
123123
}
124124
```
125+
126+
<iframe src="https://codesandbox.io/p/sandbox/9-component-children-2wrdfd?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A12%2C%22endLineNumber%22%3A19%2C%22startColumn%22%3A12%2C%22startLineNumber%22%3A19%7D%5D" width="100%" height="1000px"></iframe>

0 commit comments

Comments
 (0)