跪拜 Guibai
← All articles
React.js · TypeScript · Code Standards

Polishing a WebGPU Chat UI: Progress Bars, Byte Formatting, and Controlled Inputs

By kisshyshy ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Building a polished UI for on-device AI means sweating details like byte formatting, defensive prop handling, and controlled form inputs. These patterns are the difference between a brittle demo and an interface that stays predictable as state complexity grows.

Summary

The third installment of a series building a browser-based AI chat interface replaces a crude text-only progress display with a real, width-animated progress bar driven by percentage props. A `formatBytes` function converts raw byte counts into human-readable strings like "63.74 MB" using logarithmic math, while the nullish coalescing assignment operator (`??=`) guards against undefined props.

The chat input is implemented as a React controlled component, where a state variable is the single source of truth for the textarea's value. TypeScript type assertions (`as HTMLTextAreaElement`) resolve the generic event target, and keyboard event handling maps Enter to message submission while preserving Ctrl+Enter for newlines. The submit handler resets the UI state machine to a loading condition, ready for the model inference that will be wired up in the next part of the series.

Takeaways
`percentage ??= 0` guards against undefined props by assigning zero only when the value is null or undefined, avoiding the falsy-value traps of the older `||` operator.
`formatBytes` determines the unit index via `Math.log(size) / Math.log(1024)`, converts the value, and appends the unit from a lookup array, returning strings like "63.74 MB".
The progress bar uses an outer `w-full` container with `overflow-hidden` and an inner div whose inline `style={{ width: \`${percentage}%\` }}` makes the bar grow declaratively.
`whitespace-nowrap` on the inner progress div prevents text from wrapping and breaking the bar's single-line layout when the bar is narrow.
React controlled components make state the single source of truth: `value={input}` binds display, and `onInput` calls `setInput` to update state, creating an explicit unidirectional cycle.
TypeScript's synthetic event `target` is typed as `EventTarget`, which lacks `value`; `as HTMLTextAreaElement` asserts the concrete element type so the property can be accessed.
Enter-key submission checks `e.key === 'Enter'` and `!e.ctrlKey`, calls `e.preventDefault()` to suppress the default newline, and only fires when `input.length > 0`.
The `onSubmit` handler transitions the UI state machine to `loading`, sets a reasoning message, and clears previous errors and progress, preparing for future model inference.
Conclusions

The `??=` operator is underused in many React codebases; its narrow focus on null/undefined makes it a safer default than `||` for numeric props where zero is a valid value.

Teaching `formatBytes` through logarithms rather than a loop or chain of conditionals is a good example of using math to keep utility functions compact and branch-free.

The insistence on controlled components over two-way binding is framed as a debugging advantage: data flow is always explicit and traceable, which matters more as forms grow complex.

Type assertions in event handlers are a recurring friction point for React+TypeScript newcomers; the explanation that `EventTarget` is deliberately generic and must be narrowed is a practical bridge concept.

Concepts & terms
Nullish coalescing assignment (`??=`)
An ES2021 operator that assigns a value to a variable only if that variable is currently `null` or `undefined`. Unlike `||`, it does not treat empty strings or zero as falsy, making it safer for numeric defaults.
Controlled component
A React form element whose value is driven by component state. The state is the single source of truth; user input fires an event handler that updates state, which re-renders the element with the new value, creating an explicit unidirectional data flow.
Type assertion (`as`)
A TypeScript syntax that tells the compiler to treat a value as a specific, narrower type without any runtime checking. Used here to narrow a generic `EventTarget` to `HTMLTextAreaElement` so the `value` property can be accessed.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗