intuitive icon indicating copy to clipboard operation
intuitive copied to clipboard

Components using `use_state` cannot be rendered conditionally

Open ebenpack opened this issue 2 years ago • 3 comments

Components using use_state cannot be safely rendered conditionally. Doing so can lead to a panic, with the following error message: "insufficient calls".

Here is a minimal reproducible example: https://gist.github.com/ebenpack/3b0c3a1a2b813740003f6a4161ce8ecf

This appears to be due to the "hook rules", however it's surprising that these rules must be adhered to for the scope of the entire application. I would expect the rule to be scoped to a single component.

I'm not sure if the way this rule is applied is intended or not, but if it is I think the library would be much more usable with a more locally applied rule. E.g. it would make conditional rendering much easier and safer.

ebenpack avatar Nov 24 '22 18:11 ebenpack

Hi, thanks for opening this. I'm actually in the process of a rewrite that will fix this. My original implementation of the hooks and rendering flow is fundamentally flawed, and does not allow for conditional rendering.

enricozb avatar Nov 24 '22 20:11 enricozb

Small update:

I have a branch up for the 0.7.0 version of Intuitive, which allows for conditional rendering. It's also available as a crate as version 0.7.0-alpha.0, with the new documentation here. This isn't ready for release yet as it doesn't have any key or mouse handling.

The new documentation doesn't have an equivalent "hook rules" section yet, but it's much closer (if not the same) to React's hooks, where, within a component there can be no conditional usages of hooks, but across components that is fine.

The unmount.rs example on the 0.7.0 branch shows usages of hooks with conditional renders, and some new hooks and unmount logic.

enricozb avatar Dec 13 '22 07:12 enricozb

@enricozb: Defining the hooks in a non-conditional parent component, and passing them to the conditional child components is a way to work around this issue, right?

Something like this:

#[component(Root)]
pub fn render() {
    let step = use_state(|| Step::One);
    let one_state = use_state(|| 1);
    let two_state = use_state(|| 1);

    let current_step = match step.get() {
        Step::One => One::new(step, one_state).render(),
        Step::Two => Two::new(two_state).render(),
    };

    render! {
        Embed(content: current_step)
    }
}

intuitive looks pretty neat, and I'd love to give it a try.

PS: Also, out of curiosity, if you don't mind, do you have any update regarding the rewrite? :)

d4h0 avatar Apr 21 '23 17:04 d4h0