Skip to content

Instantly share code, notes, and snippets.

@echochamber
Last active June 16, 2023 09:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save echochamber/5fe0f6f1fa2cc8972b1986c3b6318092 to your computer and use it in GitHub Desktop.
Save echochamber/5fe0f6f1fa2cc8972b1986c3b6318092 to your computer and use it in GitHub Desktop.
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.
},
})
}
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