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:
- Setup: When the
MapViewComponent
first runs, it callsuseKeyboard(setCurrentView)
. - 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. - Key Pressed: The user presses a key, say 'P'.
- Browser Notification: The web browser detects the 'P' key press and calls the
handleKeyPress
function thatuseKeyboard
provided. - 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'. - Action Triggered: Since the key was 'P', the
handleKeyPress
function calls thesetView
function (which is actuallysetCurrentView
from ourMapViewComponent
). - View State Updated: Calling
setCurrentView("map")
updates the state inMapViewComponent
, which tells the application to switch to the map view. - Cleanup: When
MapViewComponent
is no longer needed (e.g., the user navigates to a different page), theuseKeyboard
hook tells the browser to stop callinghandleKeyPress
, 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