restrustpaginationyew

Pagination and REST API


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:

  1. When a different page is pressed the old data is not removed as such. So, it looks like this ezgif com-optimize

  2. 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);
        }
  1. for page it keeps the actual value at 1 regardless of what was previously set
  2. for mint it doesn't compile because of async move to the REST API call

what I am doing wrong?


Solution

  • 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.