One bad date should not blank an entire screen

How one malformed timestamp exposed the need for local data guards, proper preview-resource cleanup, and an honest recovery path.

Published 2026-09-13. Tags: Frontend, Reliability, Product.

An admin screen can work for weeks and then open as a blank page because of one field in one record. The whole service may be healthy. The UI simply assumed that every value it received was safe to render.

In a file-management tool I worked on, a recent fix addressed several related weaknesses: an unparseable date, preview URLs recreated while someone edited text, and a screen with no useful recovery path after a rendering error. The broader lesson is not about galleries. One bad value should not make an entire task impossible.

The problem: a small assumption inside a large list

A gallery renders many items, each with a title, image, size, and modification date. Formatting that date can look harmless:

new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' })
  .format(new Date(value));

But `new Date(value)` can produce an invalid Date without throwing. The `format` call is what throws a `RangeError`. When that happens during an item's render, the error can propagate to the whole screen. A person trying to work with dozens of valid items sees nothing because one optional value is malformed.

That is a mismatch between the data contract and the display contract. Even if the service usually returns valid timestamps, the interface still needs a defined behavior for a missing or invalid optional field.

First fix: contain the bad field

The modification date was not required to view the file or edit its description. I therefore checked it before formatting:

const formatDate = (value: string): string => {
  if (!value) return '';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return '';
  return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }).format(date);
};

If the value is invalid, only the date disappears. The title, image, and controls remain available. This is not a rule to ignore all bad data. A missing field that determines the record's identity deserves different handling. A display-only date is a reasonable place for local degradation.

The regression test should not stop at `formatDate('')`. It renders an item without a usable date and checks that its title and edit controls are still present. That verifies the user's task, not just the helper function.

Second fix: a preview URL has a lifecycle

The same screen also showed a preview of a file selected for upload. Creating its URL inside render seems convenient:

<img src={URL.createObjectURL(file)} alt="" />

But every keystroke in a description field can trigger another render and create another `blob:` URL. The old URL does not disappear just because the JSX changed. A preview URL is a browser resource: create it when the `File` changes, and call `URL.revokeObjectURL` when the preview is no longer needed.

I moved that responsibility into a small preview component. It holds one URL per `File` object, releases it during cleanup, and displays a fallback if URL creation fails. A test edits the description several times and checks that `createObjectURL` was called once rather than once per keystroke.

This is separate from the invalid-date failure. It does not, on its own, explain a blank screen. It removes resource churn and the accidental coupling between typing text and creating a file preview.

A recovery path, without disguising the defect

Even after guarding known fields, a component can still throw unexpectedly. A screen-level Error Boundary can show a recovery message and reload action instead of leaving empty space. The underlying error still needs to be recorded and investigated.

That distinction matters: the boundary does not repair malformed data. It limits the impact when local checks were not enough. Recovery copy must also stay honest. It should not claim that a file was saved unless that operation was actually verified.

What I learned

UI reliability is not only about whether the API responds. It is also about the size of the failure when one field violates an assumption:

The principle I took from this change is simple: when a failure is local, its effect on the user should be local too.