Skip to content

[Feature]: Rethink Type Safety and Flexibility of the view! Macro for Conditional Logic #3378

Description

@wiseaidev

Hiya!

I've been using Leptos for a while now (ever since building this crate), and I've noticed that working with the view! macro can be quite cumbersome when adding conditional logic (if-else) inside the macro. Let me explain with some examples:

The Problem

To add if-else logic in view!, it always has to be wrapped in a closure ({move ||}). Here's a complex example:

<div class="wallet-info">
    {move ||
        if connected.get() {
            Some(view!{
                {move ||
                    if let Some(key) = phantom_wallet_adapter.get().public_key() {
                        view!{
                            <p>"Connected Wallet: " {move || phantom_wallet_adapter.get().name()} </p>
                            <p>"Connected Public Key: " {move || key.to_string() } </p>
                            <div class="forms">
                                <div class="send-sol-form">
                                    <h2 class="form-title">{ "Transfer SOL" }</h2>
                                    <form on:submit={transfer_sol_phantom}>
                                        <div class="form-group">
                                            <label for="destination-address">
                                                { "Destination Address" }
                                            </label>
                                            <input
                                                id="destination-address"
                                                type="text"
                                                class="form-control"
                                                node_ref={input_dest_ref}
                                                required=true
                                                value=dest
                                            />
                                        </div>
                                        <div class="form-group">
                                            <label for="sol-amount">
                                                { "SOL Amount (in lamports)" }
                                            </label>
                                            <input
                                                id="sol-amount"
                                                type="number"
                                                class="form-control"
                                                node_ref={input_amount_ref}
                                                required=true
                                                value=amount
                                            />
                                        </div>
                                        <button type="submit" class="submit-button">{ "Send" }</button>
                                    </form>
                                </div>
                                <div class="sign-form">
                                    <h2 class="form-title">{ "Sign Message" }</h2>
                                    <form on:submit={sign_msg_phantom}>
                                        <div class="form-group">
                                            <label for="message">
                                                { "Message" }
                                            </label>
                                            <input
                                                id="Message"
                                                type="text"
                                                class="form-control"
                                                node_ref={input_msg_ref}
                                                required=true
                                                value=msg
                                            />
                                        </div>
                                        <button type="submit" class="submit-button">{ "Sign" }</button>
                                    </form>
                                </div>
                            </div>
                            {move ||
                                if confirmed.get() {
                                    Some(view!{
                                        <div class="transaction-info">
                                            <p>{ "Transaction Successful!" }</p>
                                            <a
                                                href={format!("https://solscan.io/tx/{}", sig.get())}
                                                target="_blank"
                                                rel="noopener noreferrer"
                                                class="view-transaction-button"
                                            >
                                                { "View Transaction" }
                                            </a>
                                        </div>
                                    })
                                } else {
                                    None
                                }
                            }
                        }
                    } else if let Some(key) = solflare_wallet_adapter.get().public_key() {
                        view!{
                            <p>"Connected Wallet: " {move || solflare_wallet_adapter.get().name()} </p>
                            <p>"Connected Public Key: " {move || key.to_string() } </p>
                        }
                    } else {
                        view!{
                            <p>"Connected but no wallet info available"</p>
                            <p>{}</p>
                        }
                    }
                }
            })
        } else {
            None
        }
    }
</div>

This setup becomes messy and hard to read or maintain when handling complex structures.

  1. Closure Wrapping: Every conditional requires {move || }.
  2. Tag Type Consistency: Both branches of the if-else must use the same HTML tag type, making it impossible to render different tags (e.g., <input> in one branch and <textarea> in the other).
  3. Attribute Type Consistency: Even attributes within the same tag must have the same type in both branches. For example:
if something {
    view! {
        <input class="str type" />
    }
} else {
    view! {
        <input class={"string type".to_string()} />
    }
}

The above fails because the class attribute types differ (&str vs. String).

Real-World Frustration
When I was building a crate to simplify input components for WASM frameworks, I found these limitations made it frustrating to handle dynamic, conditional logic while keeping the code clean and maintainable.

Suggestion

To improve usability and maintainability of the view! macro:

  1. Remove Tag-Type Restrictions: Allow different HTML tag types in each branch of the if-else if optional, when wrapped with Some().
    if something {
        view! {
            Some(<input />)
        }
    } else {
        view! {
            Some(<textarea />
        }
    }
  2. Relax Attribute Type Matching: Automatically coerce attribute types to avoid unnecessary type mismatches.
  3. Simplify Closures: Minimize or remove the need for {move ||} closures to simplify writing dynamic logic.

This would make it far easier to write concise and readable logic inside view!, especially for more complex use cases.

Thanks for considering this! 😊

Best!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions