Frontend: new Endpoints
In this chapter we will do the following:
- Creating a helper to create a new list
- Adapt our controller functions to the new routes, we defined in the backend - so we can build again :)
Creating a helper to create a new list
In order to create a new list, we have to write a new controller function and add it to our controllers.rs
module. As you already know, how to do this one - go ahead and this to your controllers.rs
:
#![allow(unused)] fn main() { async fn create_list() -> Result<CreateListResponse, reqwest::Error> { let response = reqwest::Client::new() .get("http://localhost:3001/list") .send() .await? .json::<CreateListResponse>() .await?; Ok(response) } }
This will make a GET
request to our /list
endpoint - and in return we will receive an uuid for our new list.
Adapt controllers to new endpoints
As you remember - we changed our endpoints in our backend
. Go ahead and change the other controllers.
As an example we look at the existing delete_item
function.
#![allow(unused)] fn main() { pub async fn delete_item(item_id: &str) -> Result<(), reqwest::Error> { reqwest::Client::new() .delete(format!("http://localhost:3001/items/{}", item_id)) .send() .await?; Ok(()) } }
As we now have to know in which list we want to delete the item from, we have to expand the arguments list for a list_id
(the lists uuid).
You can just append the argument to the format!(..)
string. In the end, the function should look like this:
#![allow(unused)] fn main() { pub async fn delete_item(list_id: &str, item_id: &str) -> Result<(), reqwest::Error> { reqwest::Client::new() .delete(format!( "http://localhost:3001/list/{}/items/{}", list_id, item_id )) .send() .await?; Ok(()) } }
As the other functions are kind of straightforward - go ahead and change the other functions that are still tuned to the "old" REST api.
#![allow(unused)] fn main() { pub async fn get_items(list_id: &str) -> Result<Vec<ShoppingListItem>, reqwest::Error> { let url = format!("http://localhost:3001/list/{}/items", list_id); let list = reqwest::get(&url) .await? .json::<Vec<ShoppingListItem>>() .await; list } pub async fn post_item( list_id: &str, item: PostShopItem, ) -> Result<ShoppingListItem, reqwest::Error> { let response = reqwest::Client::new() .post(format!("http://localhost:3001/list/{}/items", list_id)) .json(&item) .send() .await? .json::<ShoppingListItem>() .await?; Ok(response) } }
Propagate the list uuid from top to down
Now we need the list's uuid everywhere, where we fetch data from the backend. So somehow we need a uuid for this. Do you have a guess how?
Go to your Home component
and add a list_uuid
signal there. For now we hardcode the value to be 9e137e61-08ac-469d-be9d-6b3324dd20ad
(the first existing list in our backend - if you remember - also hardcoded ;)).
The idea now: propagate down this list_uuid
to all components, that need a list_uuid
, which are the ShoppingList
and the ItemInput
components (and all the components they use).
#![allow(unused)] fn main() { #[component] pub fn Home() -> Element { let list_uuid = use_signal(|| "9e137e61-08ac-469d-be9d-6b3324dd20ad".to_string()); let change_signal = use_signal(|| ListChanged); rsx! { ShoppingList{list_uuid, change_signal} ItemInput{list_uuid, change_signal} } } }
Of course, those components have to be adjusted in their parameters (or so called: Component props
). Go ahead and change the props of those. Then you can use the list_uuid
in the requests fired by the hooks used in the components. The easiest way to accomplish this is forwarding the signal, and read it inside the component.
To spare you more headaches - use these adjusted components. The only thing that changed, is that they now all use the added list_uuid
.
ShoppingListItemComponent
#![allow(unused)] fn main() { #[component] fn ShoppingListItemComponent( display_name: String, posted_by: String, list_uuid: String, item_id: String, change_signal: Signal<ListChanged>, ) -> Element { rsx! { div { class: "flex items-center space-x-2", p { class: "grow text-2xl", "{display_name}" } span { "posted by {posted_by}" } ItemDeleteButton {list_uuid, item_id, change_signal} } } } }
ShoppingList
#![allow(unused)] fn main() { #[component] pub fn ShoppingList(list_uuid: Signal<String>, change_signal: Signal<ListChanged>) -> Element { let items_request = use_resource(move || async move { change_signal.read(); get_items(list_uuid.read().as_str()).await }); match &*items_request.read_unchecked() { Some(Ok(list)) => rsx! { div { class: "grid place-items-center min-h-500", ul { class: "menu bg-base-200 w-200 rounded-box gap-1", for i in list { li { key: "{i.uuid}", ShoppingListItemComponent{ display_name: i.title.clone(), posted_by: i.posted_by.clone(), list_uuid, item_id: i.uuid.clone(), change_signal }, } } } } }, Some(Err(err)) => { rsx! { p { "Error: {err}" } } } None => { rsx! { p { "Loading items..." } } } } } }
ItemInput
#![allow(unused)] fn main() { #[component] pub fn ItemInput(list_uuid: Signal<String>, change_signal: Signal<ListChanged>) -> Element { let mut item = use_signal(|| "".to_string()); let mut author = use_signal(|| "".to_string()); let onsubmit = move |_| { spawn({ async move { let item_name = item.read().to_string(); let author = author.read().to_string(); let response = post_item( list_uuid.read().as_str(), PostShopItem { title: item_name, posted_by: author, }, ) .await; if response.is_ok() { change_signal.write(); } } }); }; ... } }
ItemDeleteButton
#![allow(unused)] fn main() { #[component] fn ItemDeleteButton( list_uuid: String, item_id: String, change_signal: Signal<ListChanged>, ) -> Element { let onclick = move |_| { spawn({ let list_uuid = list_uuid.clone(); let item_id = item_id.clone(); async move { let response = delete_item(&list_uuid, &item_id).await; if response.is_ok() { change_signal.write(); } } }); }; rsx! { button { onclick: onclick, class: "btn btn-circle", svg { class: "h-6 w-6", view_box: "0 0 24 24", stroke: "currentColor", stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", fill: "none", path { d: "M6 18L18 6M6 6l12 12" } } } } } }
After you changed these components - your frontend should build again by the way :) You can test it with again with
cargo make --no-workspace dev
In the next chapter we will add a new component, that creates a new list or gets a list uuid by user input.
You almost made it :) hang on!