Hi, I am building a small webapp in yew and have a problem with lifetimes in the html macro that I do not fully understand. The code is here (Branch "router"!): Files · router · andreas.pfeil / Fairris · GitLab
I have several components. The Model Component is the "main" one, and it contains a List of structs PidInfo
, which happen to be also components, but they do not have a link or props or messages. So I instantiate them like a notmal rust struct. I see in the console that this generates warnings, but thats a different problem. I can just make them normal structs with a view function I think.
The PidInfo structs render basically as a routerbutton which redirects to a Route which shall display more information. And this is where the problems start.
struct Model {
pub link: ComponentLink<Self>,
pub known_pids: Vec<PidInfo>,
}
// PID means persistent identifier, they are unique IDs to thos items.
pub struct PidInfo { pub pid: String, ... }
impl Default for PidInfo {...}
impl Component for PidInfo {
...
fn view(&self) -> Html {
let pid = self.pid.clone();
html! {
<RouterButton<AppRoute> route=AppRoute::Details(pid) classes="piditem">
<p>{ self.pid.clone() }</p>
<p>{ self.description.clone() }</p>
<p>{ self.status.clone() }</p>
</RouterButton<AppRoute>>
}
}
}
#[derive(Switch, Debug, Clone)]
pub enum AppRoute {
#[to = "/create"]
CreateFdo,
#[to = "/fdo/{id}"]
Details(String),
#[to = "/search"]
Search,
#[to = "/"]
Index,
}
The router handling was basically adapted from one of the yew examples and is integrated in the Model
s view function within the html macro:
impl Component for Model {
type Message = Msg;
type Properties = ();
...
fn view(&self) -> Html {
html! {
<div id="everything"> // this can be reduced to <></>
<div id="sidebar" class="maincolumns">
<div id="pidbuttons">
<RouterButton<AppRoute> route=AppRoute::CreateFdo>{ "+" }</RouterButton<AppRoute>>
<RouterButton<AppRoute> route=AppRoute::Search>{ "search" }</RouterButton<AppRoute>>
<button onclick=self.link.callback(|_| Msg::Remove)>{ "-" }</button> // TODO this should create a callback to remove a pid.
</div>
<div id="workspace" class="scroll-vertical">
{ for self.known_pids.iter().map(|pidinfo| pidinfo.render()) }
</div>
</div>
<Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
match switch {
AppRoute::CreateFdo => html!{<CreateComponent/>},
AppRoute::Details(pid) => {
html!{}
//(method1) self.view_pid_details(pid) will not work: lifetime error
//(dummy) html!{<DetailsComponent/>}, will work, but not receive the Item or a link
//(method2) html!{<DetailsComponent model_link=self.link.clone() item=self.get_item(pid) />}, will result in the same lifetime error.
},
AppRoute::Search => html!{<SearchComponent/>},
AppRoute::Index => html!{<CreateComponent/>},
}
})
/>
</div>
}
}
}
Please note that method1 does not render the PidInfo component but uses just it's content. This is the dummy implementetion of this detailsview:
pub fn view_pid_details(&self, pid: String) -> Html {
let pid = self.find_pidinfo_by_string(pid).pid;
html!{
<div id="content" class="maincolumns scroll-vertical">
<div class="image-placeholder"><p>{ "IMAGE" }</p></div>
<div class="textblock">
<p>{ format!("{}: {}", "PID:", pid) }</p>
<p>{ "Print PID Record here." }</p>
...
</div>
<div class="fdo-actions"><p>{ "Placeholder for Action Buttons here." }</p></div>
<div class="action-placeholder"><p>{ "Placeholder for action visualization. This could be i.e. viewing raw metadata, visualizations, or the possibility to update your FDO." }</p></div>
</div>
}
}
But even if the method does just nothing, consumes no data etc, the compiler will complain.
If I try the commented out sections method1 or method2, the resulting error looks like this:
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/lib.rs:87:63
|
87 | <Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
| _______________________________________________________________^
88 | | match switch {
89 | | AppRoute::CreateFdo => html!{<CreateComponent/>},
90 | | AppRoute::Details(pid) => {
... |
96 | | }
97 | | })
| |_____________________^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 74:5...
--> src/lib.rs:74:5
|
74 | / fn view(&self) -> Html {
75 | | html! {
76 | | <div id="everything">
77 | | <div id="sidebar" class="maincolumns">
... |
100 | | }
101 | | }
| |_____^
note: ...so that the types are compatible
--> src/lib.rs:87:63
|
87 | <Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
| _______________________________________________________________^
88 | | match switch {
89 | | AppRoute::CreateFdo => html!{<CreateComponent/>},
90 | | AppRoute::Details(pid) => {
... |
96 | | }
97 | | })
| |_____________________^
= note: expected `&&Model`
found `&&Model`
= note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/lib.rs:87:63: 97:22 self:&&Model]` will meet its required lifetime bounds
--> src/lib.rs:87:48
|
87 | <Router<AppRoute, ()> render = Router::render(|switch: AppRoute| {
| ^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
I am not even sure to which lifetimes this refers, I just have some theories. But I failed to express those ideas in lifetimes annotations. I implemented a fn do_nothing(&self) {}
method for Model
, and even this is a problem for the compiler if I call it instead of the empty html!{} macro. Whenever I try to do something with self in there, this happens. I tried cloning or borrowing the pid and nothing seemed to work. The versions of my dependencies are:
[dependencies]
wasm-bindgen = "^0.2"
yew = "0.17"
yew-router = "0.14"
wee_alloc = "0.4"
log = "0.4"
wasm-logger = "0.2"
How can I use self to do something in the routers match statement? Is there a better/less lifetime-error prone way to do the routing in yew? I think method1 should be the easiest as I do not have to copy over the Item to another component (compared to method2).
Thank you.