Yew: Difficulty with nested callbacks - rust

I'm attempting to do something that I feel is pretty basic: I have a pulldown, and I'd like the onchange event for that pulldown to cause the program to fetch some data from the backend based on the user's input. (And then, you know, give the user more options based on the first thing they picked. Really simple, and seems like I ought to have been able to find an easy way to do this.)
Full code for this minimal (failing) example is at: https://github.com/djmcmath/broken-yew
But the relevant bit, which doesn't behave correctly, is below:
The view function renders, delightfully, an iterated list. I pass in a callback, so it knows what to do on the "onchange" event.
The callback gets executed, which makes me very happy. But it isn't calling the Msg::GetData. This compiles, which is nice, but it doesn't work, which is less nice.
I've spent, I'm ashamed to admit, several weeks of my spare time fighting with this. I think it has something to do with scopes and lifetimes. I think that the way I'm making this compile -- by cloning the context and using "move" disconnects it from the actual context that I need to make this work. But every variation on the theme that I've been able to find in examples and references complains about scope or lifetimes.
Thanks in advance for the help.
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::GetData(value) => {
log::info!("Start 'fetch' with user-selected value: {}", value);
ctx.link().send_future(async {
match fetch_markdown("url_shortened_for_clarity").await {
Ok(md) => Msg::SetMarkdownFetchState(FetchState::Success(md)),
Err(err) => Msg::SetMarkdownFetchState(FetchState::Failed(err)),
}
});
false
},
Msg::SetMarkdownFetchState(fetch_state) => {
let mut wr = WebReturn { term_id: 0, dow: 0, dep_time_num: 0 };
match fetch_state {
FetchState::Success(s) => { wr = serde_json::from_str(&s).expect(&format!("Poorly formatted JSON! {}", s).to_string()); },
FetchState::Failed(f) => { log::info!("Fetch failed: {}", f); },
FetchState::NotFetching => {},
FetchState::Fetching => {}
};
log::info!("term_id (3) : {}, dep_time_num (12000) : {}, and dow (3) : {}", wr.term_id, wr.dep_time_num, wr.dow);
true
}
}
}
fn view(&self, ctx:&Context<Self>) -> Html {
let ctx_link = ctx.link().clone();
let my_callback: Callback<String> = Callback::from(move |value: String| {
let val_as_num = value.parse::<i32>().unwrap_or(0);
log::info!("Returned value: {}", val_as_num);
ctx_link.callback(|val_as_num: i32| Msg::GetData(val_as_num));
});
html! {
<div>
{ self.render_list(&self.props.term_list, my_callback) }
</div>
}
}

This line does not "call back" to your component, it creates a callback and then doesn't call it:
ctx_link.callback(|val_as_num: i32| Msg::GetData(val_as_num));
You need to instead call .send_message() in your callback or, better yet, create your original callback with .callback():
let my_callback = ctx_link.callback(|value: String| {
let val_as_num = value.parse::<i32>().unwrap_or(0);
log::info!("Returned value: {}", val_as_num);
Msg::GetData(val_as_num)
});

Related

Why does a value created inside a function borrow and how can I avoid this pattern?

I'm new to rust but an engineer of over 6 years in various other languages from Javascript to Go.
I'm wondering why here the value is borrowed when I convert the response body to an "object".
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here. Can someone set me straight?
let response = match self
.client
.index(IndexParts::IndexId(index, id))
.body(json!({
"index": index,
"body": doc,
}))
.send()
.await
{
Ok(response) => response,
Err(err) => {
return Err(Box::new(err));
}
};
let response_body = match response.json::<Value>().await {
Ok(response_body) => response_body,
Err(err) => {
return Err(Box::new(err));
}
};
let response_map = response_body.as_object();
Ok(response_map)
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here.
You need to return an owned value, not a reference into a local. I assume what you're doing now boils down to:
fn foo() -> &Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
x.as_object().unwrap() // except you do proper error handling
}
This doesn't compile because you're returning the reference to a local value. Instead, you need to return the value itself:
fn foo() -> Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
match x {
Value::Object(o) => o,
_ => unreachable!(), // you'd return Err(...)
}
}
But even this is more complicated than you need. Since you already deserialize the value yourself, and handle the errors, you can simply ask serde to deliver a Map<String, Value> to begin with:
let response_body = match response.json::<Map<String, Value>>().await {
Ok(response_body) => response_body,
Err(err) => ...
};
Of course, you'll also need to adjust the return type to return the actual value instead of a reference.

Accept multiple values on proc macro attribute

I wanted to be able to retrieve the content from an attribute like this:
#[foreign_key(table = "some_table", column = "some_column")]
This is how I am trying:
impl TryFrom<&&Attribute> for EntityFieldAnnotation {
type Error = syn::Error;
fn try_from(attribute: &&Attribute) -> Result<Self, Self::Error> {
if attribute.path.is_ident("foreign_key") {
match attribute.parse_args()? {
syn::Meta::NameValue(nv) =>
println!("NAME VALUE: {:?}, {:?}, {:?}",
nv.path.get_ident(),
nv.eq_token.to_token_stream(),
nv.lit.to_token_stream(),
),
_ => println!("Not interesting")
}
} else {
println!("No foreign key")
}
// ... More Rust code
}
Everything works fine if I just put in there only one NameValue. When I add the comma,
everything brokes.
The only error:
error: unexpected token
How can I fix my logic to enable the possibility of have more than just one NameValue?
Thanks
UPDATE: While writing this answer, I had forgotten that Meta has List variant as well which gives you NestedMeta. I would generally prefer doing that instead of what I did in the answer below for more flexibility.
Although, for your particular case, using Punctuated still seems simpler and cleaner to me.
MetaNameValue represents only a single name-value pair. In your case it is delimited by ,, so, you need to parse all of those delimited values as MetaNameValue instead.
Instead of calling parse_args, you can use parse_args_with along with Punctuated::parse_terminated:
use syn::{punctuated::Punctuated, MetaNameValue, Token};
let name_values: Punctuated<MetaNameValue, Token![,]> = attribute.parse_args_with(Punctuated::parse_terminated).unwrap(); // handle error instead of unwrap
Above name_values has type Punctuated which is an iterator. You can iterate over it to get various MetaNameValue in your attribute.
Updates based on comments:
Getting value out as String from MetaNameValue:
let name_values: Result<Punctuated<MetaNameValue, Token![,]>, _> = attr.parse_args_with(Punctuated::parse_terminated);
match name_values {
Ok(name_value) => {
for nv in name_value {
println!("Meta NV: {:?}", nv.path.get_ident());
let value = match nv.lit {
syn::Lit::Str(v) => v.value(),
_ => panic!("expeced a string value"), // handle this err and don't panic
};
println!( "Meta VALUE: {:?}", value )
}
},
Err(_) => todo!(),
};

Using wasm_timer in Yew to execute callback repeatedly

I'm still rather new to Rust and have a hard time wrapping my head around futures. I want to implement a "timer app" in the browser and to do so I'm using https://yew.rs/. For the timer I tried to use https://github.com/tomaka/wasm-timer/, but there are not docs and no examples. Looks like the usage is supposed to be obvious, but I don't get it.
I assume that I have to do something like:
let i = Interval::new(core::time::Duration::from_millis(250));
This should create an Interval that fires every 250ms. But what is fired? How to I specify my callback? I would expect something like:
i.somehow_specify_callback(|| { ... executed every 250ms ...});
My feeling is, that I'm somehow on the wrong path and do not get grasp Rust futures. A working example on how to make an Interval execute some code would be very appreciated.
Here is a pseudo code example for Timer component:
enum SecondsStateAction {
Increment,
}
#[derive(Default)]
struct SecondsState {
seconds: usize,
}
impl Reducible for SecondsState {
/// Reducer Action Type
type Action = SecondsStateAction;
/// Reducer Function
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
match action {
SecondsStateAction::Increment => Self { seconds: self.seconds + 1 }.into(),
}
}
}
#[function_component(Timer)]
pub fn timer() -> Html {
let seconds_state_handle = use_reducer(SecondsState::default);
use_effect_with_deps(
{
let seconds_state_handle = seconds_state_handle.clone();
move |_| {
// i intervals get out of scope they get dropped and destroyed
let interval = Interval::new(1000, move || seconds_state_handle.dispatch(SecondsStateAction::Increment));
// So we move it into the clean up function, rust will consider this still being used and wont drop it
// then we just drop it ourselves in the cleanup
move || drop(interval)
}
},
(), // Only create the interval once per your component existence
);
html! {<h1>{*seconds_state_handle}{" seconds has passed since this component got rendered"}</h1>}
}
to learn more about the hooks i used in the code visit https://yew.rs/docs/concepts/function-components/pre-defined-hooks

Uncaught Error with Webassembly using Yew Framwork

I'm using Yew to program a theme switcher that by clicking cycles through different themes.
This is my update function. It gets the current theme which is stored in shared state, depending on what would come next inside theme_cycle the theme value inside the shared state gets set to it.
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ChangeTheme => {
let theme_cycle: [&str; 3] = ["light", "dark", "rust"];
let current_theme = self.props.handle.state().theme.clone();
// eval next theme
let next_theme = match theme_cycle.iter().position(|x| x == &current_theme) {
None => theme_cycle[0].to_string(),
Some(i) => {
if i >= (current_theme.len() - 1) {
theme_cycle[0].to_string()
} else {
theme_cycle[i + 1].to_string()
}
},
};
// set next theme
self.props.handle.reduce(move |state| state.theme = next_theme.clone());
// store it inside localstorage
},
Msg::ToggleLangDropdown => self.show_dropdown = !self.show_dropdown,
};
true
}
But if the theme val inside shared state is "rust" and I click the button again that calls Msg::ChangeTheme, the theme should be set to "light" but instead my code panics and I get an "Uncaught Error: undefined" inside the Browser Console.
I've found a workaround; instead of using an array and accessing the values, I've tried to do the same task but just with iterators and made sure that the update function takes no ownership of any variable outside the function itself (I don't really know if that is really necessary though...)
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ChangeTheme => {
let theme_cycle = ["light".to_string(), "dark".to_string(), "rust".to_string()];
let current_theme = self.props.handle.state().theme.clone();
let indexof_current_theme = match theme_cycle.iter().position(|x| x.to_string() == current_theme) {
None => 0,
Some(x) => x.clone(),
};
let next_theme = match theme_cycle.iter().nth(indexof_current_theme + 1) {
None => theme_cycle.iter().nth(0).unwrap().clone(),
Some(x) => x.clone(),
};
self.props.handle.reduce(move |state| state.theme = next_theme.to_string());
},
Msg::ToggleLangDropdown => self.show_lang_dropdown = !self.show_lang_dropdown,
Msg::ToggleThemeDropdown => self.show_theme_dropdown = !self.show_theme_dropdown,
};
true
}
Still would be cool if anyone knows what I did wrong in my first attempt.

Idiomatic rust way to properly parse Clap ArgMatches

I'm learning rust and trying to make a find like utility (yes another one), im using clap and trying to support command line and config file for the program's parameters(this has nothing to do with the clap yml file).
Im trying to parse the commands and if no commands were passed to the app, i will try to load them from a config file.
Now I don't know how to do this in an idiomatic way.
fn main() {
let matches = App::new("findx")
.version(crate_version!())
.author(crate_authors!())
.about("find + directory operations utility")
.arg(
Arg::with_name("paths")
...
)
.arg(
Arg::with_name("patterns")
...
)
.arg(
Arg::with_name("operation")
...
)
.get_matches();
let paths;
let patterns;
let operation;
if matches.is_present("patterns") && matches.is_present("operation") {
patterns = matches.values_of("patterns").unwrap().collect();
paths = matches.values_of("paths").unwrap_or(clap::Values<&str>{"./"}).collect(); // this doesn't work
operation = match matches.value_of("operation").unwrap() { // I dont like this
"Append" => Operation::Append,
"Prepend" => Operation::Prepend,
"Rename" => Operation::Rename,
_ => {
print!("Operation unsupported");
process::exit(1);
}
};
}else if Path::new("findx.yml").is_file(){
//TODO: try load from config file
}else{
eprintln!("Command line parameters or findx.yml file must be provided");
process::exit(1);
}
if let Err(e) = findx::run(Config {
paths: paths,
patterns: patterns,
operation: operation,
}) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
There is an idiomatic way to extract Option and Result types values to the same scope, i mean all examples that i have read, uses match or if let Some(x) to consume the x value inside the scope of the pattern matching, but I need to assign the value to a variable.
Can someone help me with this, or point me to the right direction?
Best Regards
Personally I see nothing wrong with using the match statements and folding it or placing it in another function. But if you want to remove it there are many options.
There is the ability to use the .default_value_if() method which is impl for clap::Arg and have a different default value depending on which match arm is matched.
From the clap documentation
//sets value of arg "other" to "default" if value of "--opt" is "special"
let m = App::new("prog")
.arg(Arg::with_name("opt")
.takes_value(true)
.long("opt"))
.arg(Arg::with_name("other")
.long("other")
.default_value_if("opt", Some("special"), "default"))
.get_matches_from(vec![
"prog", "--opt", "special"
]);
assert_eq!(m.value_of("other"), Some("default"));
In addition you can add a validator to your operation OR convert your valid operation values into flags.
Here's an example converting your match arm values into individual flags (smaller example for clarity).
extern crate clap;
use clap::{Arg,App};
fn command_line_interface<'a>() -> clap::ArgMatches<'a> {
//Sets the command line interface of the program.
App::new("something")
.version("0.1")
.arg(Arg::with_name("rename")
.help("renames something")
.short("r")
.long("rename"))
.arg(Arg::with_name("prepend")
.help("prepends something")
.short("p")
.long("prepend"))
.arg(Arg::with_name("append")
.help("appends something")
.short("a")
.long("append"))
.get_matches()
}
#[derive(Debug)]
enum Operation {
Rename,
Append,
Prepend,
}
fn main() {
let matches = command_line_interface();
let operation = if matches.is_present("rename") {
Operation::Rename
} else if matches.is_present("prepend"){
Operation::Prepend
} else {
//DEFAULT
Operation::Append
};
println!("Value of operation is {:?}",operation);
}
I hope this helps!
EDIT:
You can also use Subcommands with your specific operations. It all depends on what you want to interface to be like.
let app_m = App::new("git")
.subcommand(SubCommand::with_name("clone"))
.subcommand(SubCommand::with_name("push"))
.subcommand(SubCommand::with_name("commit"))
.get_matches();
match app_m.subcommand() {
("clone", Some(sub_m)) => {}, // clone was used
("push", Some(sub_m)) => {}, // push was used
("commit", Some(sub_m)) => {}, // commit was used
_ => {}, // Either no subcommand or one not tested for...
}

Resources