Last active
June 16, 2023 09:31
-
-
Save echochamber/5fe0f6f1fa2cc8972b1986c3b6318092 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use crate::search::search_service::{SearchService, SearchErr, SearchResponse}; | |
use dioxus::{prelude::*}; | |
use log::debug; | |
// Render this in another page. | |
// | |
// I think of this as a "default layout" component, since you can still use InitFormAndDisplay to do you own layout. | |
pub fn DefaultComponent(cx: Scope) -> Element { | |
let (form, display) = InitFormAndDisplay(cx); | |
cx.render(rsx!{ | |
form | |
div { hr {} } | |
display | |
}) | |
} | |
// Example of a different layout you can import instead. | |
pub fn FormOnBottomLayout(cx: Scope) -> Element { | |
let (form, display) = InitFormAndDisplay(cx); | |
cx.render(rsx!{ | |
display | |
div { hr {} } | |
form | |
}) | |
} | |
// Initializes two components: SearchForm and SearchDisplay. | |
// | |
// The reason we do this is the shared state's lifetime needs to outlive the components that use it | |
// So we initialize the shared state here and pass each child component a handle to it (we are | |
// lifting the state). | |
// | |
// Since our return type is (Element, Element), we can't use this directly in rsx!{} but you | |
// can see we can still use it above in DefaultComponent. | |
pub fn InitFormAndDisplay (cx: Scope) -> (Element, Element) { | |
// Shared state between the form and the display. | |
let search_result: &UseState<Option<Result<SearchResponse, SearchErr>>> = use_state(cx, || None); | |
let search_handler = move |evt: FormEvent| { | |
// cx.spawn here is only necessary because we are doing an async call to an external API. | |
// Otherwise, just do implement handler logic here, no cx.spawn needed. | |
cx.spawn({ | |
// Do stuff with the result from the event. | |
let query = evt.values["query"].clone(); | |
// We need to move search result into the future, but it needs to live the below async block | |
// to avoid borrow checker errors | |
let search_result = search_result.to_owned(); | |
async move { | |
// Service wraps calling an external API using an http client(reqwest). | |
let resp: Result<SearchResponse, SearchErr> = SearchService::new().query(&query.as_str()).await; | |
// Handling response from API. | |
match &resp { | |
Ok(_data) => debug!("Query successful!"), | |
Err(_err) => debug!("Query failed") | |
} | |
// Write whatever state/data we need from the API call to the shared state, so we can use it | |
// in the SearchDisplay component. | |
search_result.set(Some(resp)); | |
} | |
}); | |
}; | |
// Return the two components. | |
( | |
cx.render(rsx! { | |
SearchForm {on_submit: move |evt: FormEvent| { search_handler(evt) } } | |
}), | |
cx.render(rsx! { | |
SearchDisplay { search_result: &search_result } | |
}) | |
) | |
} | |
// Form componenent. Pretty simple. Make sure to set prevent_default so your page doesn't reload. | |
#[inline_props] | |
fn SearchForm<'a> (cx: Scope, on_submit: EventHandler<'a, FormEvent>) -> Element<'a> { | |
cx.render(rsx! { | |
form { | |
prevent_default: "onsubmit", | |
// Our shared state was moved into the closure our on_submit wraps, which is how our form will | |
// interact with it. | |
onsubmit: move |event| on_submit.call(event), | |
input { id: "query", name: "query", } | |
button { | |
value: "Submit", | |
r#type: "submit", | |
prevent_default: true, | |
"Search", | |
} | |
} | |
}) | |
} | |
// Displays results from submitting our form and getting data from the search service. | |
#[inline_props] | |
fn SearchDisplay<'a> (cx: Scope<'a>, search_result: &'a UseState<Option<Result<SearchResponse, SearchErr>>>) -> Element<'a> { | |
// The display is much simpler, just get the data out of the shared state, and render your rsx! | |
// accordingly. | |
cx.render(match search_result.get() { | |
// You can just do regular rsx here, I'm matching because I need to handle the result of my API | |
// call from above | |
Some(Ok(response)) => { | |
rsx! { div { "It work! {response.things_found:?} "}} | |
// API call is done, render results | |
}, | |
Some(Err(e)) => { | |
rsx! { div { "It failed :( {e:?}" } } | |
}, | |
_ => rsx! { | |
rsx! { div { "Loading..."}} // Maybe a spinner icon too. | |
}, | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use serde::{Deserialize, Serialize}; | |
use std::future; | |
#[derive(Serialize, Deserialize, Debug)] | |
pub struct SearchResponse { | |
pub things_found: Vec<String> | |
} | |
#[derive(Debug)] | |
pub struct SearchErr(String); | |
pub struct SearchService {} | |
impl SearchService { | |
fn new() -> Self { | |
Self {} | |
} | |
async fn query(self, q: &str) -> Result<SearchResponse, SearchErr> { | |
let pretend_http_request = future::ready( | |
SearchResponse { | |
things_found: vec!["thing1".to_string(), "thing2".to_string()] | |
} | |
); | |
Ok(pretend_http_request.await) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment