Limits page crashes with Cannot read properties of undefined (reading 'length') for most pairs on fresh deploy #327

Closed
opened 2026-06-05 12:14:27 +00:00 by totdking · 6 comments
totdking commented 2026-06-05 12:14:27 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-06-05 12:28:08 +00:00 (Migrated from gitlab.com)

Summary

On a fresh deploy (make reset-qa / make deploy-local) with the indexer running (make indexer-dev), navigating to the Limits page and switching between pairs crashes most of them with Cannot read properties of undefined (reading 'length') rendered directly on screen. Only EMBER/CORAL loaded correctly during the observed session. The issue resolved on its own during the same session — likely after the indexer finished its initial sync.

The error implies a .length read on a value that was undefined at render time, reaching the screen rather than being caught by an error boundary that shows a graceful message.


Root cause (suspected, unconfirmed — requires developer investigation)

Two code paths are the most likely candidates based on static analysis:

Candidate 1 — getAllPairsPaginated in factory.ts:44:

const resp = await getAllPairs(startAfter, PAGE_SIZE)
if (resp.pairs.length === 0) break    // ← no optional chaining

getAllPairs calls queryContract, which returns data.data as T from the LCD response. If the LCD node or the factory contract returns a response where pairs is absent (e.g. during initial startup before contract state is ready), resp.pairs is undefined and resp.pairs.length crashes with exactly "Cannot read properties of undefined (reading 'length')".

This runs inside a useQuery queryFn, so React Query catches it and stores it as pairsQuery.isError = true. Whether the error then propagates to a rendered crash depends on how upstream code handles pairsQuery.isError — if no graceful error state is shown for that case, the component tree may crash.

Candidate 2 — indexer /limit-book returning a response without orders for unsynced pairs:

useLimitBookInfinite calls getPairLimitBookPage, which expects IndexerLimitBookPageResponse: { orders: IndexerShallowLimitOrder[], has_more, ... }. If the indexer returns a page without the orders field for a pair it hasn't fully indexed yet, p.orders is undefined. The OrderBookPanel derives orders via:

const orders = q.data?.pages.flatMap((p) => p.orders) ?? []

When p.orders is undefined, flatMap returns [undefined] (the ?? [] fallback doesn't trigger because the result is a non-null array). Downstream rendering of BookRow then accesses order.owner, order.order_id, order.price, etc. on the undefined item, crashing at the first unguarded property read. The specific .length error may arise in a string operation on one of these fields.

Why EMBER/CORAL works: EMBER/CORAL is the first pair deployed and likely the first pair fully indexed by the indexer. Pairs deployed after it may not yet have their limit-book entries populated when the frontend first queries them.


Steps to reproduce

  1. Run make reset-qa or make deploy-local to get a fresh deploy
  2. Start the indexer: make indexer-dev
  3. Start the frontend: make dev
  4. Open the Limits page (/limits)
  5. Connect Keplr wallet
  6. Switch between available pairs using the pair dropdown
  7. Observe: most pairs render Cannot read properties of undefined (reading 'length') directly on screen
  8. Observe: EMBER/CORAL (or the first indexed pair) loads correctly
  9. Wait several minutes for the indexer to finish its initial sync
  10. Observe: error clears on its own (no page reload required)

Expected behavior

All pairs should either:

  • Load correctly with the order book and ticket form populated, or
  • Show a graceful loading/empty state while the indexer is syncing (e.g. spinner, "Order book unavailable while indexer syncs")

No raw JavaScript error message should be rendered to the user under any conditions. An error boundary or per-component fallback should intercept the crash and show a user-friendly message.


Actual behavior

Raw JS crash: Cannot read properties of undefined (reading 'length') is displayed directly on screen for most pairs immediately after a fresh deploy. The error is transient — it self-resolves after the indexer finishes syncing. No actionable message is shown to the user.


Additional notes

  • The error did not require a page reload to clear — it resolved in the same browser session
  • Console logs and a network trace at the time of the crash would be needed to confirm whether the crash originates from the LCD pairs query (Candidate 1) or the indexer limit-book query (Candidate 2)
  • Recommended first step: add console.log in getAllPairsPaginated and getPairLimitBookPage on a fresh deploy to capture the raw response before the crash

Environment

  • Chain: localterra
  • LCD: http://localhost:1317
  • Stack: fresh deploy via make reset-qa / make deploy-local
  • Wallet: Keplr (Terra Classic)
  • Browser: Chrome
  • Page: /limits
  • Indexer: running (make indexer-dev), initial sync in progress at time of crash
  • Network throttle applied: No

Severity: P2(polish) not a permanent breakage, but reproducible on every fresh deploy and exposes a raw JS crash to the user with no error message or fallback. Needs root cause confirmation before a fix can be scoped.

cc: @PlasticDigits

Related checklist items: EH-2, EH-3

### Summary On a fresh deploy (`make reset-qa` / `make deploy-local`) with the indexer running (`make indexer-dev`), navigating to the Limits page and switching between pairs crashes most of them with `Cannot read properties of undefined (reading 'length')` rendered directly on screen. Only EMBER/CORAL loaded correctly during the observed session. The issue resolved on its own during the same session — likely after the indexer finished its initial sync. The error implies a `.length` read on a value that was `undefined` at render time, reaching the screen rather than being caught by an error boundary that shows a graceful message. --- ### Root cause (suspected, unconfirmed — requires developer investigation) Two code paths are the most likely candidates based on static analysis: **Candidate 1 — `getAllPairsPaginated` in `factory.ts:44`:** ```ts const resp = await getAllPairs(startAfter, PAGE_SIZE) if (resp.pairs.length === 0) break // ← no optional chaining ``` `getAllPairs` calls `queryContract`, which returns `data.data as T` from the LCD response. If the LCD node or the factory contract returns a response where `pairs` is absent (e.g. during initial startup before contract state is ready), `resp.pairs` is `undefined` and `resp.pairs.length` crashes with exactly "Cannot read properties of undefined (reading 'length')". This runs inside a `useQuery queryFn`, so React Query catches it and stores it as `pairsQuery.isError = true`. Whether the error then propagates to a rendered crash depends on how upstream code handles `pairsQuery.isError` — if no graceful error state is shown for that case, the component tree may crash. **Candidate 2 — indexer `/limit-book` returning a response without `orders` for unsynced pairs:** `useLimitBookInfinite` calls `getPairLimitBookPage`, which expects `IndexerLimitBookPageResponse: { orders: IndexerShallowLimitOrder[], has_more, ... }`. If the indexer returns a page without the `orders` field for a pair it hasn't fully indexed yet, `p.orders` is `undefined`. The `OrderBookPanel` derives `orders` via: ```ts const orders = q.data?.pages.flatMap((p) => p.orders) ?? [] ``` When `p.orders` is `undefined`, `flatMap` returns `[undefined]` (the `?? []` fallback doesn't trigger because the result is a non-null array). Downstream rendering of `BookRow` then accesses `order.owner`, `order.order_id`, `order.price`, etc. on the `undefined` item, crashing at the first unguarded property read. The specific `.length` error may arise in a string operation on one of these fields. **Why EMBER/CORAL works:** EMBER/CORAL is the first pair deployed and likely the first pair fully indexed by the indexer. Pairs deployed after it may not yet have their `limit-book` entries populated when the frontend first queries them. --- ### Steps to reproduce 1. Run `make reset-qa` or `make deploy-local` to get a fresh deploy 2. Start the indexer: `make indexer-dev` 3. Start the frontend: `make dev` 4. Open the Limits page (`/limits`) 5. Connect Keplr wallet 6. Switch between available pairs using the pair dropdown 7. Observe: most pairs render `Cannot read properties of undefined (reading 'length')` directly on screen 8. Observe: EMBER/CORAL (or the first indexed pair) loads correctly 9. Wait several minutes for the indexer to finish its initial sync 10. Observe: error clears on its own (no page reload required) --- ### Expected behavior All pairs should either: - Load correctly with the order book and ticket form populated, or - Show a graceful loading/empty state while the indexer is syncing (e.g. spinner, "Order book unavailable while indexer syncs") No raw JavaScript error message should be rendered to the user under any conditions. An error boundary or per-component fallback should intercept the crash and show a user-friendly message. --- ### Actual behavior Raw JS crash: `Cannot read properties of undefined (reading 'length')` is displayed directly on screen for most pairs immediately after a fresh deploy. The error is transient — it self-resolves after the indexer finishes syncing. No actionable message is shown to the user. --- ### Additional notes - The error did not require a page reload to clear — it resolved in the same browser session - Console logs and a network trace at the time of the crash would be needed to confirm whether the crash originates from the LCD `pairs` query (Candidate 1) or the indexer `limit-book` query (Candidate 2) - Recommended first step: add `console.log` in `getAllPairsPaginated` and `getPairLimitBookPage` on a fresh deploy to capture the raw response before the crash --- ### Environment - Chain: localterra - LCD: [http://localhost:1317](http://localhost:1317) - Stack: fresh deploy via `make reset-qa` / `make deploy-local` - Wallet: Keplr (Terra Classic) - Browser: Chrome - Page: `/limits` - Indexer: running (`make indexer-dev`), initial sync in progress at time of crash - Network throttle applied: No --- **Severity:** P2(polish) not a permanent breakage, but reproducible on every fresh deploy and exposes a raw JS crash to the user with no error message or fallback. Needs root cause confirmation before a fix can be scoped. cc: @PlasticDigits **Related checklist items:** EH-2, EH-3
ghost1 commented 2026-06-05 12:41:18 +00:00 (Migrated from gitlab.com)

mentioned in commit 499e642c5a

mentioned in commit 499e642c5a0670a1c4fda2f748ca8a09c39b51d4
ghost1 commented 2026-06-05 12:41:18 +00:00 (Migrated from gitlab.com)

mentioned in commit c09b71a0e4

mentioned in commit c09b71a0e48f8694d3601951bd0405f73993adc9
PlasticDigits commented 2026-06-05 12:41:40 +00:00 (Migrated from gitlab.com)

mentioned in merge request !803

mentioned in merge request !803
PlasticDigits commented 2026-06-05 12:41:48 +00:00 (Migrated from gitlab.com)

Implementation (agent:implement)

MR: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/85

Confirmed root cause: missing orders on limit-book pages made flatMap((p) => p.orders) produce [undefined], which crashed render/hint code (often surfacing as Cannot read properties of undefined (reading 'length') via the error boundary). Secondary guard added for missing pairs in factory pagination.

Acceptance

Item How verified Status
No raw JS crash on pair switch; empty book or outage UI instead Unit tests (OrderBookPanel, LimitOrdersPage, client normalization) PASS
Graceful handling while indexer syncs (missing orders) normalizeLimitBookPageResponse + flattenLimitBookPages tests PASS
Factory pairs query robustness factory.test.ts missing pairs page PASS
User-friendly error boundary copy if something else throws humanizeUserFacingError.test.ts PASS
Manual fresh-deploy repro from issue steps Not run on this VM (no full stack in session) SKIP

Blocker for closing: manual fresh-deploy verification on MR merge (issue steps 1–10).

## Implementation (agent:implement) MR: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/85 Confirmed root cause: missing `orders` on limit-book pages made `flatMap((p) => p.orders)` produce `[undefined]`, which crashed render/hint code (often surfacing as `Cannot read properties of undefined (reading 'length')` via the error boundary). Secondary guard added for missing `pairs` in factory pagination. ### Acceptance | Item | How verified | Status | |------|----------------|--------| | No raw JS crash on pair switch; empty book or outage UI instead | Unit tests (`OrderBookPanel`, `LimitOrdersPage`, client normalization) | PASS | | Graceful handling while indexer syncs (missing `orders`) | `normalizeLimitBookPageResponse` + `flattenLimitBookPages` tests | PASS | | Factory `pairs` query robustness | `factory.test.ts` missing `pairs` page | PASS | | User-friendly error boundary copy if something else throws | `humanizeUserFacingError.test.ts` | PASS | | Manual fresh-deploy repro from issue steps | Not run on this VM (no full stack in session) | SKIP | Blocker for closing: manual fresh-deploy verification on MR merge (issue steps 1–10).
PlasticDigits commented 2026-06-05 13:07:12 +00:00 (Migrated from gitlab.com)

mentioned in commit 9d97e98108

mentioned in commit 9d97e98108c3e4314dbaefb56f5cdceb177803aa
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-05 13:07:17 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#327
No description provided.