← All Workshops

MudEngine Part 6: Multiplayer

Step 7 / 12

Name Registration — App Component

The App component is the entry point. On first visit it shows a name input screen. There is no persistent state yet — each page load starts fresh.

Once the player enters a name and clicks the button (or presses Enter), the Game component mounts, which establishes the WebSocket connection.

The two-component split is important: the Game component's use_websocket and use_future hooks only run after the player provides a name. This avoids connecting with an empty name.

mud-engine/src/main.rs
// ── Name Registration ──

#[component]
fn App() -> Element {
    let mut player_name = use_signal(|| String::new());
    let mut registered = use_signal(|| false);

    if !*registered.read() {
        rsx! {
            div { class: "name-screen",
                h1 { "🧙 MudEngine" }
                p { class: "subtitle", "A multiplayer adventure awaits. What is your name?" }

                Input {
                    style: "padding: 10px 14px; font-size: 16px; border: 2px solid #3b82f6; border-radius: 6px; background: #12122a; color: #d0d0e0; width: 280px; outline: none; font-family: 'Courier New', Courier, monospace; box-sizing: border-box;",
                    value: "{player_name}",
                    oninput: move |e: FormEvent| player_name.set(e.value()),
                    onkeydown: move |e: KeyboardEvent| {
                        if e.key() == Key::Enter
                            && !player_name.read().trim().is_empty()
                        {
                            registered.set(true);
                        }
                    },
                    placeholder: "Enter your name...",
                }

                Button {
                    variant: ButtonVariant::Primary,
                    style: "margin: 16px auto 0; display: block;",
                    onclick: move |_| {
                        if !player_name.read().trim().is_empty() {
                            registered.set(true);
                        }
                    },
                    "Enter the World"
                }
            }
        }
    } else {
        rsx! {
            Game { name: player_name() }
        }
    }
}
💡 Why two components?

Dioxus hooks (use_signal, use_future, use_websocket) must be called in the same order on every render. Conditional rendering that changes which hooks are called is only safe when the condition stays stable — here, once registered is true, it never flips back, so the Game component mounts exactly once.

If we tried to call use_websocket inside the same component as the name input, we'd need to conditionally skip the hook call when not registered, breaking the hook order rules. The two-component pattern avoids this cleanly.

Step 7 / 12