This sounds like something very basic to implement but somehow I cannot figure it out. I'm building a website that shows some data on a page. Since there is a lot of data I want to display it in a paginated way. What I'm struggling with is the state management. Let me first show the code
I have a route with a path segment:
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
#[at("/:token_address")]
Collection { token_address: String },
}
Then I have 4 different Collections
(different token_address
) in the header
component (see the gif below)
And I have the Collection function_component
itself:
#[derive(Properties, PartialEq)]
pub struct Props {
pub token_address: String,
}
#[function_component(Collection)]
pub fn collection_function_component(props: &Props) -> Html {
let page = use_state(|| 1);
let mint = use_state(|| None);
{
let token_address = props.token_address.clone();
let mint = mint.clone();
let page_val = *page;
info!("page_val {} token {}", page_val, token_address);
use_effect_with_deps(
move |_| {
wasm_bindgen_futures::spawn_local(async move {
match api_utils::fetch_single_api_response::<MintData>(
format!("/mint/mints?token_address={}&page={}", token_address, page_val).as_str(),
).await
{
Ok(fetched_mint) => {
mint.set(Some(fetched_mint));
}
Err(e) => {
error!("{e}")
}
}
});
},
(page_val, props.token_address.clone()),
);
}
// mint and page response handling logic
}
I omitted the mint
and page
response handling logic, but inside there I update the page
state whenever the pagination buttons are pressed. No update to the mint
state is performed.
It works, but there are two issues with this:
When a different page is pressed the old data is not removed as such. So, it looks like this
Whenever I switch between different Collection token_addresses
the page state is not reset, the logs are showing that as well
page_val 3 token 0x9e0d99b864e1ac12565125c5a82b59adea5a09cd
page_val 3 token 0x844a2a2b4c139815c1da4bdd447ab558bb9a7d24
page_val 3 token 0xc1f1da534e227489d617cd742481fd5a23f6a003
page_val 3 token 0x9e0d99b864e1ac12565125c5a82b59adea5a09cd
I've seen the Caution statement about using use_effect_with_deps
and use_state
, but I do have the page_val
as dependent already.
I thought I should use TearDown
of use_effect_with_deps
to reset both states but it doesn't work
move || {
page.set(1);
mint.set(None);
}
page
it keeps the actual value at 1
regardless of what was previously setmint
it doesn't compile because of async move
to the REST API callwhat I am doing wrong?
An acceptable solution was actually quite simple - set the state to None
before querying a new one, like this:
#[function_component(Collection)]
pub fn collection_function_component(props: &Props) -> Html {
let page = use_state(|| 1);
let mint = use_state(|| None);
{
let token_address = props.token_address.clone();
let mint = mint.clone();
let page_val = *page;
info!("page_val {} token {}", page_val, token_address);
use_effect_with_deps(
move |_| {
mint.set(None) // <-- added this
wasm_bindgen_futures::spawn_local(async move {
match api_utils::fetch_single_api_response::<MintData>(
format!("/mint/mints?token_address={}&page={}", token_address, page_val).as_str(),
).await
{
Ok(fetched_mint) => {
mint.set(Some(fetched_mint));
}
Err(e) => {
error!("{e}")
}
}
});
},
(page_val, props.token_address.clone()),
);
}
}
With that the state is reset and the view is re-rendered with None
state, so you would need to make sure you have something rendered there, e.g.:
return match (*mint).as_ref() {
Some(mint) => {
html! {
// render mints
}
}
None => {
html! {
// add a loading spinner, for example
}
}
};
P.S.
I omitted the page
related logic as it was reworked in my code and no longer needed. But the same approach can be applied there as well.