← All Workshops

MudEngine Part 6: Multiplayer

Step 11 / 18

App Component — Name Registration

The App component is the entry point. On first visit it shows a name input screen, pre-filled with the generated name. 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.

Replace src/components/app.rs with this version. It imports the Game component and the random_name helper from the game module:

mud-engine/src/components/app.rs
use crate::components::game::Game;
use crate::game::random_name::random_name;
use dioxus::prelude::*;

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

    rsx! {
        document::Stylesheet { href: asset!("/assets/main.css") }
        document::Stylesheet { href: asset!("/assets/dx-components-theme.css") }
        if !*registered.read() {
            div { class: "name-screen",
                h1 { "🧙 MudEngine" }
                p { class: "subtitle", "A multiplayer adventure awaits. What is your name?" }

                input {
                    class: "name-input",
                    value: "{player_name}",
                    oninput: move |e| player_name.set(e.value()),
                    onkeydown: move |e| {
                        if e.key() == Key::Enter
                            && !player_name.read().trim().is_empty()
                        {
                            registered.set(true);
                        }
                    },
                    placeholder: "Enter your name...",
                }

                button {
                    class: "join-btn",
                    onclick: move |_| {
                        if !player_name.read().trim().is_empty() {
                            registered.set(true);
                        }
                    },
                    "Enter the World"
                }
            }
        } else {
            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 11 / 18