Skip to content

Step 1 of 5 · Reading · ~3 min

Read

Virtual DOM & Diffing

Event Handling

When the user clicks, the browser must decide which handlers run and in what order. After this lesson your program makes that decision for an element tree: the propagation algorithm of the DOM, which a framework reuses when it routes events through its own tree.

An event travels in three phases

An event fired on an element (the target) does not stay there. It travels the path from the root to the target and back:

 root      1. capture, going down       3. bubble, going up
  |
 form
  |
 button    2. at the target
  1. Capture: from the root down to the target's parent. Only listeners registered with the capture flag run.
  2. Target: at the target, listeners registered with the capture flag run first, then all the others, each group in registration order.
  3. Bubble: from the target's parent up to the root. Only the ordinary listeners run.

A listener is ordinary unless it asks for capture, so with no flags you see the familiar order: target first, then its parent, then the grandparent.

Stopping, and events that do not bubble

stopPropagation ends the trip, but not instantly. Every other listener on the same element and phase still runs; the trip stops before the next step. One consequence is easy to miss. The target is visited twice, once for its capture listeners and once for the rest, so a capture listener at the target that stops the event prevents the target's own ordinary listeners from running.

Some events do not bubble: focus, blur, mouseenter and mouseleave. They still capture and still reach the target, but the third phase is skipped. A focus listener on a form therefore never fires for an input inside it unless the form asked for capture. This is why focusin and focusout, which do bubble, exist, and why React uses them under the hood.

What a framework adds

Handlers arrive as props whose names start with on. Creating a node attaches them and diffing a node swaps them. Every render creates a brand-new arrow function for onClick, so a naive "detach the old, attach the new" would churn on every render. Frameworks instead attach one stable listener per node once, and store the newest handler where that listener can find it.

React goes further and attaches one native listener per event type on the root container. When an event arrives it walks its own tree from the target upward and calls the handlers it finds, exactly the three-phase walk above, with the tree as data. The count of native listeners stays flat however many nodes you render, and because the propagation is synthesised, the event object can be normalised across browsers and stopPropagation stops the walk where you expect. The exercise is that walk.

Two traps that live next to events

A stale closure is an effect or timer created in one render that keeps seeing that render's values. An interval created on the first render with count equal to 0 computes 1 forever. Nothing is broken; closures work as specified. The fix is not to read the captured value: ask the framework for the latest with a functional update.

A controlled input takes its value from state, and every keystroke goes through a handler that updates state. The component is the single source of truth, so you can validate or reject on the way. An uncontrolled input leaves the value in the DOM and reads it on submit.

Common mistakes

  • Running bubble listeners before capture listeners, or treating the capture flag as "run first at every node".
  • Dropping the rest of an element's listeners when one stops propagation.
  • Bubbling a focus event.
  • Ignoring that listeners are specific to an event name, so a keydown listener must not run for a click.
  • Letting a stop at the target in the capture phase still run the target's ordinary listeners.

Your exercise

The starter reads the tree and the listeners and prints a header for each event. You write dispatch, which decides which handler ids run and in what order.

Up nextKeyed List DiffingVirtual DOM & Diffing

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…