Skip to content

fix(OneDateNavigator): revive persisted string dates before use#4733

Merged
sauldom102 merged 1 commit into
mainfrom
fix/date-navigator-revive-persisted-dates
Jul 17, 2026
Merged

fix(OneDateNavigator): revive persisted string dates before use#4733
sauldom102 merged 1 commit into
mainfrom
fix/date-navigator-revive-persisted-dates

Conversation

@sauldom102

@sauldom102 sauldom102 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Problem

OneDateNavigator crashes with TypeError: from.getTime is not a function when its value/defaultValue has been restored from persisted OneDataCollection storage.

Any OneDataCollection with a versioned id (storage on) that declares a date-navigator navigation filter persists the selected range to localStorage. navigationFilters is in the persisted-feature allowlist (dataCollectionStorageFeatures), so the { from: Date, to: Date } range is written via JSON.stringify — turning the dates into ISO strings.

On the next load, the storage restore path applies the parsed value straight back into live state:

// dataCollectionLocalStorageHandler.ts
get: async (keyName) => JSON.parse(localStorage.getItem(getKey(keyName)) ?? "{}")  // from/to are now strings
// useDataCollectionStorage.ts
featureProvider.setValue(featureValue)    // pushed back verbatim, no date revival

That restored value flows into OneDateNavigator, whose equality check and trigger call Date methods on it:

  • isSameDatePickerValuea.value?.from.getTime()
  • DateNavigatorTrigger.from.getTime(), granularity.toString(value.value), getPrevNext(value.value)

"2026-07-13T…".getTime() doesn't exist → crash. It reproduces on the second load (once a serialized range is sitting in storage); a clean first load works because the initial default is normalized through the filter's valueConverter, which the restore path bypasses.

Originally surfaced on Factorial's /projects/contributors/list after that collection adopted a date-navigator filter.

Regression origin

This is a latent defect, not caused by any recent release. The two ingredients were introduced separately, and it became reproducible once both were present:

  • feat/datenavigator and navigation filters in datacolletions #1828 (2025-05-19) — feat/datenavigator and navigation filters in datacollections — added the date-navigator navigation filter.
  • feat/datacollection sort hide cols #2637 (2025-09-25) — feat/datacollection sort hide colsthe cause: introduced the OneDataCollection localStorage persistence system, added navigationFilters to the persisted-feature set, and shipped dataCollectionLocalStorageHandler which JSON.stringifys state on write and bare JSON.parses it on read with no date revival. From this commit on, any collection combining storage + a date-navigator filter is one reload away from the crash.
  • feat: datenavigator value prop #2841 (2025-10-22) — feat: datenavigator value prop — added isSameDatePickerValue, one of the two .getTime() sites that throw on a string from.

Fix

Revive value/defaultValue into real Date objects at the OneDateNavigator boundary (reviveDatePickerValue), so every downstream consumer — the equality check, the trigger label, granularity math — receives Dates regardless of where the value came from. It's a no-op that returns the same reference when the range already holds Date objects, so there's no added re-render churn.

Fixing at this boundary (rather than only patching isSameDatePickerValue) is deliberate: the trigger also calls .getTime()/granularity math, so a comparator-only patch would just relocate the crash.

Tests

Added two regression tests to OneDateNavigator.test.tsx covering a defaultValue and a controlled value whose dates were serialized to strings — both previously threw, now render "January 2023".

  • vitest (OneDateNavigator + DatePickerPopup) — 16 passed
  • oxlint / oxfmt --check clean on changed files
  • tsc clean on changed files

🤖 Generated with Claude Code

A date-navigator value restored from OneDataCollection persisted storage
round-trips through JSON, so its `from`/`to` arrive as ISO strings rather than
Date objects. OneDateNavigator's equality check and its trigger (label,
granularity math) call Date methods like `.getTime()` on them, throwing
`TypeError: from.getTime is not a function` on the second load of any
collection that persists a `date-navigator` navigation filter.

Revive `value`/`defaultValue` into real Date objects at the OneDateNavigator
boundary, so every downstream consumer receives Dates. It's a no-op (returns
the same reference) when the range already holds Date objects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added fix react Changes affect packages/react labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ No New Circular Dependencies

No new circular dependencies detected. Current count: 0

@github-actions

Copy link
Copy Markdown
Contributor

📦 Alpha Package Version Published

Use pnpm i github:factorialco/f0#npm/alpha-pr-4733 to install the package

Use pnpm i github:factorialco/f0#235e9cc5dd1c05daa05bf72fb3645f4ef98b5ad5 to install this specific commit

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Visual review for your branch is published 🔍

Here are the links to:

@github-actions

Copy link
Copy Markdown
Contributor

✅ No breaking public API changes

No public exports were removed, renamed, or had existing props/types changed in a breaking way compared to main.

Comparing f0, experimental and ai against main. Adding components, types, or optional props is safe. This check is non-blocking.

⚠️ Could not analyze component-status (no-base) — a build may have failed; results may be incomplete.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for packages/react

Status Category Percentage Covered / Total
🔵 Lines 59.3% 20759 / 35003
🔵 Statements 58.35% 21766 / 37302
🔵 Functions 51.44% 4819 / 9367
🔵 Branches 52.02% 14980 / 28796
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/react/src/patterns/OneDateNavigator/OneDateNavigator.tsx 85.71% 82.35% 66.66% 85.18% 82-83, 94-97, 129
packages/react/src/ui/DatePickerPopup/utils.ts 100% 94.73% 100% 100%
Generated in workflow #15733 for commit 1afe0d7 by the Vitest Coverage Report Action

@sauldom102
sauldom102 marked this pull request as ready for review July 17, 2026 09:59
@sauldom102
sauldom102 requested a review from a team as a code owner July 17, 2026 09:59
@sauldom102
sauldom102 merged commit 6d6fcbe into main Jul 17, 2026
26 of 30 checks passed
@sauldom102
sauldom102 deleted the fix/date-navigator-revive-persisted-dates branch July 17, 2026 10:08
@eliseo-juan eliseo-juan mentioned this pull request Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix react Changes affect packages/react

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant