pub fn use_effect(callback: impl FnMut() + 'static) -> Effect
Expand description
Effects are reactive closures that run after the component has finished rendering. Effects are useful for things like manually updating the DOM after it is rendered with web-sys or javascript. Or reading a value from the rendered DOM.
Effects are specifically created for side effects. If you are trying to derive state, use a memo, or resource instead.
If you are trying to update the DOM, you can use the use_effect
hook to run an effect after the component has finished rendering.
use_effect
will subscribe to any changes in the signal values it captures effects will always run after first mount and then whenever the signal values change. If the use_effect call was skipped due to an early return, the effect will no longer activate.
fn MyComponent() -> Element {
let mut count = use_signal(|| 0);
use_effect(move || {
// Effects are reactive like memos, and resources. If you read a value inside the effect, the effect will rerun when that value changes
let count = count.read();
// You can use the count value to update the DOM manually
document::eval(&format!(
r#"var c = document.getElementById("dioxus-canvas");
var ctx = c.getContext("2d");
ctx.font = "30px Arial";
ctx.fillText("{count}", 10, 50);"#
));
});
rsx! {
button {
// When you click the button, count will be incremented and the effect will rerun
onclick: move |_| count += 1,
"Increment"
}
canvas {
id: "dioxus-canvas",
}
}
}
§With non-reactive dependencies
To add non-reactive dependencies, you can use the crate::use_reactive()
hook.
Signals will automatically be added as dependencies, so you don’t need to call this method for them.
#[component]
fn Comp(count: u32) -> Element {
// Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
use_effect(use_reactive((&count,), |(count,)| println!("Manually manipulate the dom") ));
todo!()
}
§Modifying mounted nodes
One of the most common use cases for effects is modifying or reading something from the rendered DOM. Dioxus provides access to the DOM with the onmounted
event.
You can combine use_effect
with onmounted
to run an effect with access to a DOM element after all rendering is finished:
fn MyComponent() -> Element {
let mut current_text = use_signal(String::new);
let mut mounted_text_div: Signal<Option<MountedEvent>> = use_signal(|| None);
let mut rendered_size = use_signal(String::new);
use_effect(move || {
// If we have mounted the text div, we can read the width of the div
if let Some(div) = mounted_text_div() {
// We read the current text here inside of the effect instead of the spawn so the effect subscribes to the signal
let text = current_text();
spawn(async move {
let bounding_box = div.get_client_rect().await;
rendered_size.set(format!("{text} is {bounding_box:?}"));
});
}
});
rsx! {
input {
// When you enter text into the input, the effect will rerun because it subscribes to the current_text signal
oninput: move |evt| current_text.set(evt.value()),
placeholder: "Enter text here",
value: "{current_text}"
}
// When text changes, it will change the size of this div
div {
onmounted: move |element| {
mounted_text_div.set(Some(element.clone()));
},
"{current_text}"
}
"{rendered_size}"
}
}
§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.