The branch-switch crash
Business view
Dentolize clinics can have several branches (locations). Some staff — typically owners or managers — work across more than one branch and switch between them from the branch selector in the app.
Each branch has its own opening hours, breaks, holidays, rooms, and doctor roster. Whenever the app needs to answer "is this branch open at this time, and does it have a free slot?", it needs that branch's details loaded first.
The first time a user switched to a branch in a given session — one whose details hadn't been fetched and cached yet — there was a small window where the app already knew which branch was selected, but didn't yet have that branch's opening-hours data back from the server. If the user reached one of a few specific appointment-scheduling screens during that window, the app crashed outright with a blank error screen instead of a normal loading state. Switching to a previously visited branch (or reloading) didn't trigger it, because the data was already cached — which made the bug easy to miss in casual testing and more likely to surprise users at multi-branch clinics with several locations.
This PR does not change what any button does. It changes what happens during that narrow loading window: instead of crashing, the affected screens now wait gracefully (showing an empty or default state) until the branch data arrives.
Technical view
The data source
All three affected components fetch branch data the same way — an Apollo useQuery(BRANCH_DETAILS, { variables: { branch: selectedBranch }, skip: !selectedBranch, fetchPolicy: 'cache-first' }). See it in:
packages/clinic-web/src/components/dashboard/appointments/AppointmentForm/CheckRepeatTimesAvailabilityButton.js:40-44packages/clinic-web/src/components/dashboard/appointments/AppointmentForm/MonthlyCalendar.js:47-51packages/clinic-web/src/components/dashboard/appointments/AppointmentForm/MultipleTimesButton.js:25-29(already guarded — see below)
selectedBranch comes from useSelectedBranch() (packages/clinic-web/src/context/branchContext.js), a plain useState with no persistence — it's whatever the user picked this session.
Because fetchPolicy is cache-first and the query is keyed by selectedBranch, switching to a branch id Apollo hasn't cached yet forces a real network round-trip. Until that resolves, data (aliased to branchData at each call site) is undefined. This is normal, expected Apollo behavior — the bug was never in the data-fetching, only in how the three affected components consumed it.
Where it broke
getStartAndEndTimes(value, branchData, moment)in
packages/clinic-mobile/src/components/dashboard/calendar/newAppointment/calendarHelpers.js read branchData.branchDetails.opens[dayIndex] unconditionally. Called with branchData === undefined, branchData.branchDetails throws TypeError: Cannot read properties of undefined (reading 'branchDetails'). This helper is shared by both the mobile app and (via a cross-package import) the web app, so the same missing guard affected both platforms.
CheckRepeatTimesAvailabilityButtoncomputedparsedBranchDataby
spreading branchData.branchDetails on every render, not just when its modal was open. That computation sat directly in the component body, so simply rendering the "Available Slots" button while branchData was still undefined was enough to throw — before the user had even clicked anything.
MonthlyCalendar'sdataByDaymemo builtparsedBranchDatafrom
branchData.branchDetails with only an if (!data) return [] guard — nothing checked branchData itself. Because two independent queries (DATE_RANGE_APPOINTMENTS for data, BRANCH_DETAILS for branchData) fire in parallel when the month picker opens, data could easily resolve first, and the memo would run with branchData still undefined.
A crash inside a React render or a useMemo callback is an uncaught exception at the component-tree level — React unmounts the surrounding tree and (absent an error boundary catching it) the page goes blank. That's the "uncaught runtime error" referenced in this PR's title.
Why MultipleTimesButton didn't have this bug
packages/clinic-web/src/components/dashboard/appointments/AppointmentForm/MultipleTimesButton.js fetches branchData the same way, but already guards it in two places: it builds parsedBranchData with a branchData ? {...} : {} ternary (MultipleTimesButton.js:44-56), and — more importantly — the component renders nothing at all (<></>) until branchData exists (MultipleTimesButton.js:162-198). This PR brings the other three call sites up to the same standard rather than introducing a new pattern. See The three guards, in detail for exactly what changed in each file.