Replies: 3 comments 10 replies
-
Real reasoning behind hook limitations is that they are attached to the component in question, and have to know the order they are called in. You can think of each hook call being indexed like in following pseudo-rust-code: let a: A = use_a(); // 0 stores A
let b: B = use_b(); // 1 stores B
let c: C = use_c(); // 2 stores C If we break the order, the system will not be able to reason about the types of the values that get stored: let a: A = use_a(); // 0 stores A
if something {
let b: B = use_b(); // 1 stores B
}
let c: C = use_c(); // 2 stores C, but when `!something`, this would be 1, except 1 sometimes stores B, not C... Macros I believe early exits should be a thing. If they are not, this is what this discussion/issue is about. #[hook]
fn use_short_circuit() -> Result<(), HError> {
let h1 = use_h()?;
let h2 = use_h()?;
do_something(h1, h2);
Ok(())
} |
Beta Was this translation helpful? Give feedback.
-
You can write short circuiting with a wrapper component: #[component]
pub fn Parent() -> Html {
match use_login() {
Some(user) => html! { <WithUser {user} />,
None => html! { <Redirect />}
}
}
#[derive(PartialEq, Properties)]
pub struct WithUserProps {
user: User
}
#[component]
pub fn WithUser(props: WithUserProps) -> Html {
todo!()
} You may want to use a higher order component that is generic and can be used anywhere short circuiting with the user is required. That can be done by having the component take in a generic component and props for that component, matching against the user and calling that component. #[component]
pub fn WithUser<C: Component>(props: C::Properties) -> Html {
let user = use_user();
match user {
Some(user) => html! { <C ..props },
None => html! { <Redirect /> }
}
} |
Beta Was this translation helpful? Give feedback.
-
@hamza1311 Actually, I can't, so I was really shocked when you did at least something that resembled React HOCs. Rust type system guarantees that they do not happen, since you cannot split props into two halves and give one half for the HOC to consume and the other half for the child components or child HOCs. If you really wanted that React HOC behavior, you would have to use something like I can imagine that passing a function which would let user manually build props from whatever HOC gives could be a solution: #[derive(Properties, PartialEq)]
pub struct WithUserProps<C: Component> {
pub id: UserID,
pub props: Callback<User, C::Properties>,
}
#[component]
pub fn WithUser<C: Component>(WithUserProps { id, props }: &WithUserProps<C>) -> Html {
let user: User = use_user(id);
match user {
Some(user) => { // i noticed your example ignored `user` - this is the part of the problem
let props = props.emit(user);
html! { <C ..props /> }
},
None => html! { <Redirect /> },
}
} But for this, a better solution might be using Render Props: #[derive(Properties, PartialEq)]
pub struct UserSwitchProps {
pub id: UserID,
pub render: Callback<User, Html>, // consider calling this `children` in Yew 0.21, as it is supposedly possible to write the render callback as a child
}
#[component]
pub fn UserSwitch(UserSwitchProps { id, render }: &UserSwitchProps) -> Html { // i couldnt think of a better name
let user: User = use_user(id);
match user {
Some(user) => render.emit(user),
None => html! { <Redirect /> },
}
} Another solution might be using contexts (letting children to do #[derive(Properties, PartialEq)]
pub struct WithUserProps<C: Component> {
pub id: UserID,
pub props: Callback<(), C::Properties>,
}
#[component]
pub fn WithUser<C: Component>(WithUserProps { id, props }: &WithUserProps<C>) -> Html {
let user: User = use_user(id);
match user {
Some(user) => {
let props = props.emit(); // we gotta make props somehow
html! {
<ContextProvider<User> context={ user }>
<C ..props />
</ContextProvider<User>>
}
},
None => html! { <Redirect /> },
}
} But for this, a better solution might be using Wrapper Components (called HOCs in the docs, I kinda wanna name them Provider Components though): #[derive(Properties, PartialEq)]
pub struct UserProviderProps {
pub id: UserID,
pub children: Children,
}
#[component]
pub fn UserProvider(UserProviderProps { id, children }: &UserProviderProps) -> Html {
let user: User = use_user(id);
match user {
Some(user) => {
html! {
<ContextProvider<User> context={ user }>
{ children.clone() }
</ContextProvider<User>>
}
},
None => html! { <Redirect /> },
}
} |
Beta Was this translation helpful? Give feedback.
-
I am struggling to properly implement the following pattern inside a component.
What is the intended way of "early" breaking when using hooks? When I logout from such a component, it raises an error that the order is violated. The documentation states that this can only happen if a Suspense is returned but it seems very inelegant to define a "placeholder" use_future and then redirect in the fallback. This design is also only possible if the fallback is not used for intended purposes such as loading screen etc..
Furthermore, the harsh restrictions on hooks are (afaik) not properly documented. From some digging around it seems to be related to restricting users from using hooks in loops and so forth but no real reasoning is given.
Beta Was this translation helpful? Give feedback.
All reactions