Fix routing, data fetching, and rendering issues in CRDC demo app:
Deploy to git-pages / deploy (push) Successful in 10s

- App.jsx: Fix 'search another district' button href to use /crdc-demo/ instead of / (root)
- LoadingAnimation.jsx: Replace broken inline fetch helper with centralized api client from useApi.js — was bypassing envelope unwrapping ({status:'success',data:[...]}), causing all 43 API calls to fail silently
- ChartPanel.jsx: Same fix — replace raw fetch() calls with api.fetchDistrictEstimates(), fix national_rates.json path (remove hardcoded /crdc-demo/ prefix that breaks in dev), parallelize wave/model fetches with Promise.all for faster loading

Root causes fixed:
1. Navigation button linked to site root instead of app subdirectory → users lost their way after selecting a district
2. Dual API client implementations — inline fetch helpers didn't unwrap the JSON envelope structure, causing data parsing failures that made the page appear stuck on 'Organizing data...'
3. Sequential fetches in ChartPanel caused slow loading; parallelized for better UX
This commit is contained in:
2026-08-10 11:42:31 -04:00
parent c475bfe50b
commit 8ba7440a34
3 changed files with 30 additions and 45 deletions
+17 -27
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'
import ChartPanel from './ChartPanel.jsx'
import * as api from '../hooks/useApi.js'
// The 8 student groups we fetch for (race×sex combinations)
const STUDENT_GROUPS = [
@@ -102,18 +103,18 @@ export default function LoadingAnimation({ district, state }) {
return renderError(error, district)
}
// Build the grid of "bars" — one per API call needed
const allBars = [
...WAVES.map((y, i) => ({ label: `Wave ${y}`, subLabel: 'Time series', col: 0, row: i })),
...STUDENT_GROUPS.map((sg, i) => ({ label: sg.label, subLabel: 'Rate by group', col: 1, row: i })),
...MODEL_QUADRANTS.flatMap((q, qi) =>
STUDENT_GROUPS.map((sg, si) => ({
label: `${q.model.split('_m')[0].substring(0,3).toUpperCase()} M${qi+1}`,
subLabel: sg.label,
col: 2 + qi % 2, row: si, quadrant: q
}))
),
]
// Build the grid of "bars" — one per API call needed.
// Total = WAVES (3) + STUDENT_GROUPS (8) + MODEL_QUADRANTS×STUDENT_GROUPS (4×8=32)
const TOTAL_BARS = WAVES.length + STUDENT_GROUPS.length + (MODEL_QUADRANTS.length * STUDENT_GROUPS.length)
// Build bar metadata for rendering
const allBars = Array.from({ length: TOTAL_BARS }, (_, i) => {
if (i < WAVES.length) return { subLabel: 'Time series', group: 'waves' }
if (i < WAVES.length + STUDENT_GROUPS.length) return { subLabel: STUDENT_GROUPS[i - WAVES.length].label, group: 'groups' }
const quadIdx = Math.floor((i - WAVES.length - STUDENT_GROUPS.length) / STUDENT_GROUPS.length)
const sgIdx = (i - WAVES.length - STUDENT_GROUPS.length) % STUDENT_GROUPS.length
return { subLabel: STUDENT_GROUPS[sgIdx].label, group: 'models', modelIdx: quadIdx }
})
return (
<div style={{ padding: 'var(--space-5) 0 var(--space-7)' }}>
@@ -148,7 +149,7 @@ export default function LoadingAnimation({ district, state }) {
))}
{/* Grid of animated bars — each represents one API call */}
{allBars.slice(0, totalCalls || allBars.length).map((bar, i) => {
{allBars.map((bar, i) => {
const filled = i < loadedCount
// Random height for "data viz" aesthetic (deterministic via seed = index)
const heightSeed = ((i * 37) % 100) + 20 // 20–120px range
@@ -201,21 +202,10 @@ export default function LoadingAnimation({ district, state }) {
)
}
// ——— Inline fetch helper with CORS proxy support ———
const PROXY_URL = import.meta.env.VITE_PROXY_URL || ''
// ——— API helper using the centralized client from useApi.js ———
// This ensures consistent error handling, envelope unwrapping, and CORS proxy support.
async function fetchDistrictEstimatesBatch(leaid, year, race = null, sex = null, model = 'unified_m2_mod') {
const params = new URLSearchParams({ leaid, year, model })
if (race) params.set('race', race)
if (sex) params.set('sex', sex)
// Route through proxy to bypass CORS restrictions when deployed on pages.civilytics.org
const targetPath = `/api/v1/estimates/${leaid}?${params}`
const url = PROXY_URL ? `${PROXY_URL}?target=${encodeURIComponent(targetPath)}` : `https://crdc-api.civilytics.org/api/v1/estimates/${leaid}?${params}`
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
return api.fetchDistrictEstimates(leaid, { year, model, race, sex })
}
function getStateName(code) {