Skip to content

Chapter 5: User Interaction Handlers#

Welcome back! In the previous chapters, we've covered the essential building blocks of our building-height application: * We defined the structure of our data using Data Types & Models (Chapter 1). * We learned how to fetch building geometry and camera information using the Building Data API Client (Chapter 2). * We explored Geographic Utility Functions (Chapter 3) to calculate things like building height and area from the data. * And we saw how the 3D Building Rendering Pipeline (Chapter 4) takes this data and turns it into a visible, interactive 3D visualization on the map.

All these parts work together to get and display the building information. But how does the user control what they see? How do they tell the application to change the view, perhaps from a top-down map to a close-up 3D view, or even a first-person perspective?

Your Application's Control Panel#

Imagine the application is like a complex device, maybe a drone or a camera, that can operate in different modes (taking wide photos, zooming in, flying high, flying low). To switch between these modes, you press buttons or flip switches on a control panel.

In our building-height application, the "control panel" for interacting with the view is handled by something we can call User Interaction Handlers. This is the part of the code that listens for what the user does – like pressing keys on their keyboard – and translates those actions into commands for the application, such as changing the map's view mode.

It's the bridge between the user and the visual output we learned about in Chapter 4: 3D Building Rendering Pipeline, allowing the user to switch how they look at the rendered building.

Listening to Your Keyboard#

In our specific building-height project, one of the main ways the user interacts to change the view is by pressing keyboard keys. The application listens for these key presses. When a specific key is pressed, it triggers a predefined action: changing the view mode.

The three main view modes you can switch between are:

  • Orthographic View: A top-down, flat view (like a standard architectural plan).
  • Map View: The default, slightly tilted 3D map view.
  • First-Person View: A view from the perspective of a camera, placing you at the camera's location and looking in its direction.

The User Interaction Handler we'll look at is responsible for detecting which key the user pressed and then telling the map component to switch to the corresponding view mode.

How It Works: The useKeyboard Hook#

The building-height project uses a standard pattern in web development called a "hook" to manage this keyboard listening. The code for this is in src/hooks/useKeyboard.ts.

This useKeyboard hook is designed to be used by a component (like the main map component) that needs to react to keyboard input.

Let's see how you might set it up:

import { useKeyboard } from "./hooks/useKeyboard";
import { useState } from "react";

function MapViewComponent() {
  // We need a way to keep track of the current view mode
  // and a function to change it.
  const [currentView, setCurrentView] = useState("map"); // Start in map view

  // Use the keyboard hook, giving it the function to change the view state
  useKeyboard(setCurrentView);

  // ... rest of your component to display the map ...
  // The map rendering code (from Chapter 4's ideas) would use
  // the 'currentView' state to configure itself.

  return (
      <div>
          <p>Current View: {currentView}</p>
          {/* Your Deck.gl map rendering would go here */}
      </div>
  );
}

// To make this component actually do something, you'd render it:
// <MapViewComponent />

In this simple example, MapViewComponent uses the useState hook to hold the current view mode (currentView) and gets a function (setCurrentView) to update that state.

The key line is useKeyboard(setCurrentView);. This calls the useKeyboard hook, passing it the setCurrentView function. Inside the useKeyboard hook, this setCurrentView function will be called whenever one of the special keys ('T', 'P', 'D') is pressed, changing the currentView state in our component. This state change would then cause the map to re-render in the new mode (using the logic discussed conceptually in Chapter 4: 3D Building Rendering Pipeline, although the view transition itself is handled by the map library).

Under the Hood: Inside useKeyboard#

Let's look at the actual code in src/hooks/useKeyboard.ts to understand how it listens for key presses and calls the setView function we pass it.

The Process (Non-Code Walkthrough)#

Here's the sequence of events when a user presses a key and the useKeyboard hook is active:

  1. Setup: When the MapViewComponent first runs, it calls useKeyboard(setCurrentView).
  2. Start Listening: Inside useKeyboard, the code tells the web browser, "Please let me know every time any key is pressed on the window." It gives the browser a specific function (handleKeyPress) to call whenever this happens.
  3. Key Pressed: The user presses a key, say 'P'.
  4. Browser Notification: The web browser detects the 'P' key press and calls the handleKeyPress function that useKeyboard provided.
  5. Handler Check: The handleKeyPress function receives information about the key press (specifically, which key it was). It checks if the key is 'T', 'P', or 'D'.
  6. Action Triggered: Since the key was 'P', the handleKeyPress function calls the setView function (which is actually setCurrentView from our MapViewComponent).
  7. View State Updated: Calling setCurrentView("map") updates the state in MapViewComponent, which tells the application to switch to the map view.
  8. Cleanup: When MapViewComponent is no longer needed (e.g., the user navigates to a different page), the useKeyboard hook tells the browser to stop calling handleKeyPress, cleaning up after itself.

Here's a simple diagram:

sequenceDiagram
    participant MVC as MapViewComponent
    participant UKH as useKeyboard Hook
    participant Browser as Web Browser

    MVC->>UKH: Call useKeyboard(setCurrentView)
    UKH->>Browser: Tell Browser: "Notify me on 'keypress', call handleKeyPress"
    User->>Browser: Press Key ('P')
    Browser->>UKH: Call handleKeyPress(event for 'P')
    UKH->>UKH: Check event.key ('P')
    UKH->>MVC: Call setCurrentView("map")
    MVC->>MVC: Update view state to "map"
    MVC-->>UKH: (Cleanup when component removed)
    UKH->>Browser: Tell Browser: "Stop notifying me on 'keypress'"

Code Details#

Let's look at the specific parts of src/hooks/useKeyboard.ts.

First, the setup and cleanup using useEffect:

// src/hooks/useKeyboard.ts
import { useCallback, useEffect } from "react";

export const useKeyboard = (
  // This is the function passed from the component,
  // which updates the view state
  setView: (view: "firstPerson" | "map" | "orthographic") => void
) => {
  // ... handleKeyPress function defined here ...

  useEffect(() => {
    // When the component mounts (appears), start listening
    window.addEventListener("keypress", handleKeyPress);

    return () => {
      // When the component unmounts (disappears), stop listening
      window.removeEventListener("keypress", handleKeyPress);
    };
  }, [handleKeyPress]); // This hook depends on handleKeyPress

  // The hook itself doesn't return anything visual, just sets up the listener
};

The useEffect hook is used to manage side effects, like setting up event listeners. The code inside the useEffect function (the first argument) runs when the component that uses useKeyboard first appears. It calls window.addEventListener("keypress", handleKeyPress) to tell the browser to call our handleKeyPress function every time a key is pressed anywhere in the window.

The function returned by useEffect (return () => { ... }) is a cleanup function. It runs when the component is removed. This is crucial for preventing memory leaks; we tell the browser to stop calling handleKeyPress when we don't need it anymore by calling window.removeEventListener.

Now, let's look at the handleKeyPress function itself:

// src/hooks/useKeyboard.ts
import { useCallback, useEffect } from "react";

export const useKeyboard = (
  setView: (view: "firstPerson" | "map" | "orthographic") => void
) => {
  const handleKeyPress = useCallback(
    (event: KeyboardEvent) => {
      // Check which key was pressed (converting to uppercase for flexibility)
      switch (event.key.toUpperCase()) {
        case "T": // If 'T' is pressed
          setView("orthographic"); // Call the setView function with "orthographic"
          break;
        case "P": // If 'P' is pressed
          setView("map"); // Call the setView function with "map"
          break;
        case "D": // If 'D' is pressed
          setView("firstPerson"); // Call the setView function with "firstPerson"
          break;
        default:
          // If any other key is pressed, do nothing
      }
    },
    [setView] // This function needs access to the setView function
  );

  // ... useEffect hook defined here ...
};

The handleKeyPress function is the core logic. It receives a KeyboardEvent object which contains details about the key press, including event.key (which is the character of the key pressed, e.g., "t", "p", "d").

It uses a switch statement to check the uppercase version of the pressed key. * If it's 'T', it calls setView("orthographic"). * If it's 'P', it calls setView("map"). * If it's 'D', it calls setView("firstPerson"). * If it's anything else, the default: case does nothing.

The useCallback hook is used here to ensure that the handleKeyPress function doesn't get recreated unnecessarily every time the component updates. This is a small performance optimization detail and you don't need to worry too much about it as a beginner, just understand that handleKeyPress is the function that runs when a key is pressed.

Connecting to Previous Chapters#

This interaction handler, while simple, is essential for making the application usable. By changing the view mode, it directly impacts how the data visualized by the 3D Building Rendering Pipeline (Chapter 4) is presented. Switching to "firstPerson" view, for example, would require the rendering pipeline to use the camera's position and orientation (data that came from the API client, as discussed in Chapter 2: Building Data API Client). The view state itself ("orthographic", "map", "firstPerson") might be defined using data types similar to those discussed in Chapter 1: Data Types & Models.

This shows how the different parts of the application work together: the interaction handler responds to the user, changes the application's state (the view mode), and that state change triggers the rendering pipeline to draw the scene differently.

Conclusion#

In this chapter, we explored User Interaction Handlers, focusing on how the application responds to keyboard input to change the map view. We learned about the useKeyboard hook, which listens for specific key presses ('T', 'P', 'D') and calls a function to update the application's view state, allowing the user to switch between orthographic, map, and first-person perspectives. We looked under the hood at how useEffect is used to set up and clean up the keyboard listener, and how the handleKeyPress function checks the pressed key and triggers the appropriate view change using a switch statement.

This concludes our initial exploration of the building-height project's core concepts. We've gone from understanding the basic data structures to fetching and processing geographic information, visualizing it in 3D, and finally, enabling basic user control over the view.

This is the final chapter in this tutorial series. You now have a foundational understanding of how the key pieces of the building-height application work together!


Generated by AI Codebase Knowledge Builder