Function freya::prelude::use_resource
pub fn use_resource<T, F>(future: impl FnMut() -> F + 'static) -> Resource<T>where
T: 'static,
F: Future<Output = T> + 'static,
Expand description
use_resource()
is a reactive hook that resolves to the result of a future. It will rerun when you write to any signals you read inside the future.
§Example
use dioxus::prelude::*;
async fn get_weather(location: &WeatherLocation) -> Result<String, String> {
Ok("Sunny".to_string())
}
fn app() -> Element {
let country = use_signal(|| WeatherLocation {
city: "Berlin".to_string(),
country: "Germany".to_string(),
coordinates: (52.5244, 13.4105),
});
// Because the resource's future subscribes to `country` by reading it (`country.read()`),
// every time `country` changes the resource's future will run again and thus provide a new value.
let current_weather = use_resource(move || async move { get_weather(&country()).await });
rsx! {
// the value of the resource can be polled to
// conditionally render elements based off if it's future
// finished (Some(Ok(_)), errored Some(Err(_)),
// or is still running (None)
match &*current_weather.read_unchecked() {
Some(Ok(weather)) => rsx! { WeatherElement { weather } },
Some(Err(e)) => rsx! { p { "Loading weather failed, {e}" } },
None => rsx! { p { "Loading..." } }
}
}
}
#[derive(Clone)]
struct WeatherLocation {
city: String,
country: String,
coordinates: (f64, f64),
}
#[component]
fn WeatherElement(weather: String) -> Element {
rsx! { p { "The weather is {weather}" } }
}
§Reactivity
use_resource
is reactive which just means that it will rerun when you write to any signals you read inside the future. This means that any time you change something the future depends on, the resource automatically knows to rerun. Lets take a look at some examples:
// Create a new count signal
let mut count = use_signal(|| 1);
// Create a new resource that doubles the value of count
let double_count = use_resource(move || async move {
// Start a request to the server. We are reading the value of count in the format macro
// Reading the value of count makes the resource "subscribe" to changes to count (when count changes, the resource will rerun)
let response = reqwest::get(format!("https://myserver.com/doubleme?count={count}")).await.unwrap();
response.text().await.unwrap()
});
// Resource can be read in a way that is similar to signals, but they have a bit of extra information about the state of the resource future.
// Calling .state() on a resource will return a Signal<UseResourceState> with information about the current status of the resource
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Pending"
// You can also try to get the last resolved value of the resource with the .value() method
println!("{:?}", double_count.read()); // Prints "None"
// Wait for the resource to finish and get the value
std::thread::sleep(std::time::Duration::from_secs(1));
// Now if we read the state, we will see that it is done
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
// And we can get the value
println!("{:?}", double_count.read()); // Prints "Some(2)"
// Now if we write to count, the resource will rerun
count += 1; // count is now 2
// Wait for the resource to finish and get the value
std::thread::sleep(std::time::Duration::from_secs(1));
// Now if we read the state, we will see that it is done
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
// And we can get the value
println!("{:?}", double_count.read()); // Prints "Some(4)"
// One more case, what happens if we write to the resource while it is in progress?
// The resource will rerun and the value will be None
count += 1; // count is now 3
// If we write to a value the resource subscribes to again, it will cancel the current future and start a new one
count += 1; // count is now 4
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Stopped"
println!("{:?}", double_count.read()); // Prints the last resolved value "Some(4)"
// After we wait for the resource to finish, we will get the value of only the latest future
std::thread::sleep(std::time::Duration::from_secs(1));
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
println!("{:?}", double_count.read()); // Prints "Some(8)"
§With non-reactive dependencies
use_resource
can determine dependencies automatically with any reactive value (Signal
s, ReadOnlySignal
s, Memo
s, Resource
s, etc). If you need to rerun the future when a normal rust value changes, you can add it as a dependency with the crate::use_reactive()
hook:
#[component]
fn Comp(count: u32) -> Element {
// We manually add the resource to the dependencies list with the `use_reactive` hook
// Any time `count` changes, the resource will rerun
let new_count = use_resource(use_reactive!(|(count,)| async move {
sleep(100).await;
count + 1
}));
rsx! { "{new_count:?}" }
}
// If your value is already reactive, you never need to call `use_reactive` manually
// Instead of manually adding count to the dependencies list, you can make your prop reactive by wrapping it in `ReadOnlySignal`
#[component]
fn ReactiveComp(count: ReadOnlySignal<u32>) -> Element {
// Because `count` is reactive, the resource knows to rerun when `count` changes automatically
let new_count = use_resource(move || async move {
sleep(100).await;
count() + 1
});
rsx! { "{new_count:?}" }
}
§Differences from use_future
and use_memo
Just like crate::use_future()
, use_resource
spawns an async task in a component. However, unlike crate::use_future()
, use_resource
returns the result of the future and will rerun when any dependencies change.
Resources return a value based on some existing state just like memos, but unlike memos, resources do not memorize the output of the closure. They will always rerun any parts of your app that read the value of the resource when the future resolves even if the output doesn’t change.
See also: Resource
§Additional Information that may be useful
This function is a hook which means you need to follow the rules of hooks when you call it. You can click here to learn more about the rules of hooks.
Hooks in dioxus need to run in the same order every time you run the component. To make sure you run hooks in a consistent order, you should follow the rules of hooks:
- Hooks should only be called from the root of a component or another hook
fn App() -> Element {
// ✅ You can call hooks from the body of a component
let number = use_signal(|| 1);
if number() == 1 {
// ❌ You can run into issues if you can hooks inside other expressions inside your component
// If number changes from 0 to 1, the order of the hooks will be different and your app may panic
let string = use_signal(|| "hello world".to_string());
}
todo!()
}
fn use_my_hook() -> Signal<i32> {
// ✅ You can call hooks from the body of other hooks
let number = use_signal(|| 1);
// ❌ Again, creating hooks inside expressions inside other hooks can cause issues
if number() == 1 {
let string = use_signal(|| "hello world".to_string());
}
number
}
- Hooks should always start with
use_
to make it clear that you need to call them in a consistent order
Because hooks should only be called from the root of a component or another hook, you shouldn’t call hooks inside of:
- ❌ Conditionals
fn App() -> Element {
let number = use_signal(|| 1);
// ❌ Changing the condition will change the order of the hooks
if number() == 1 {
let string = use_signal(|| "hello world".to_string());
}
// ❌ Changing the value you are matching will change the order of the hooks
match number() {
1 => {
let string = use_signal(|| "hello world".to_string());
},
_ => (),
}
todo!()
}
- ❌ Loops
fn App() -> Element {
let number = use_signal(|| 1);
// ❌ Changing the loop will change the order of the hooks
for i in 0..number() {
let string = use_signal(|| "hello world".to_string());
}
todo!()
}
- ❌ Event Handlers
fn App() -> Element {
rsx! {
button {
onclick: move |_| {
// ❌ Calling the event handler will change the order of the hooks
use_signal(|| "hello world".to_string());
},
"Click me"
}
}
}
- ❌ Initialization closures in other hooks
fn App() -> Element {
let number = use_signal(|| {
// ❌ This closure will only be called when the component is first created. Running the component will change the order of the hooks
let string = use_signal(|| "hello world".to_string());
string()
});
todo!()
}
Why do hooks need to run in a consistent order?
Hooks need to be run in a consistent order because dioxus stores hooks in a list and uses the order you run hooks in to determine what part of the state belongs to which hook.
Lets look at an example component:
fn App() -> Element {
let number = use_signal(|| 1); // Hook 1
let string = use_signal(|| "hello world".to_string()); // Hook 2
let doubled = use_memo(move || number() * 2); // Hook 3
todo!()
}
When we first create the component, we run the hooks in the order they are defined and store the state in the component in a list.
[
Box::new(1),
Box::new("hello world".to_string()),
Box::new(2),
]
Next time we run the component, we return items from the state list instead of creating state again.
[
Box::new(1), // Hook 1 returns 1
Box::new("hello world".to_string()), // Hook 2 returns "hello world"
Box::new(2), // Hook 3 returns 2
]
The order the hooks are run it must be the same because the order determines which hook gets what state! If you run the hooks in a different order, dioxus may panic because it can’t turn the state back into the right type or you may just get the wrong state for your hook.
How should I move around state? (Click here to learn more about moving around and sharing state in Dioxus)
You will often need to move state around between your components. Dioxus provides three different ways to pass around state:
- Just pass your values as props:
fn MyComponent() -> Element {
let count = use_signal(|| 0);
rsx! {
IncrementButton {
count
}
}
}
#[component]
fn IncrementButton(mut count: Signal<i32>) -> Element {
rsx! {
button {
onclick: move |_| count += 1,
"Increment"
}
}
}
This is the most common way to pass state around. It is the most explicit and local to your component. Use this when it isn’t overly annoying to pass around a value.
- Use use_context to pass state from a parent component to all children:
#[derive(Clone, Copy)]
struct MyState {
count: Signal<i32>
}
fn ParentComponent() -> Element {
// Use context provider provides an unique type to all children of this component
use_context_provider(|| MyState { count: Signal::new(0) });
rsx! {
// IncrementButton will have access to the count without explicitly passing it through props
IncrementButton {}
}
}
#[component]
fn IncrementButton() -> Element {
// Use context gets the value from a parent component
let mut count = use_context::<MyState>().count;
rsx! {
button {
onclick: move |_| count += 1,
"Increment"
}
}
}
This is slightly less explicit than passing it as a prop, but it is still local to the component. This is really great if you want state that is global to part of your app. It lets you create multiple global-ish states while still making state different when you reuse components. If I create a new ParentComponent
, it will have a new MyState
.
- Globals let you share state with your whole app with rust statics:
// Count will be created the first time you access it with the closure you pass to Signal::global
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
fn ParentComponent() -> Element {
rsx! {
IncrementButton {}
}
}
fn IncrementButton() -> Element {
rsx! {
button {
// You don't need to pass anything around or get anything out of the context because COUNT is global
onclick: move |_| *COUNT.write() += 1,
"Increment"
}
}
}
Global state can be very ergonomic if your state is truly global, but you shouldn’t use it if you need state to be different for different instances of your component. If I create another IncrementButton
it will use the same COUNT
. Libraries should generally avoid this to make components more reusable.
Note: Even though it is in a static,
COUNT
will be different for each app instance (this is generally only reliant on the server).