Virtual Internships · 2026
Batch Selection
Batch selection was a 500-line component holding its own copy of rules that belonged to the backend — which flow the user was in, which batches they were eligible for, whether selection was locked. Frontend state mirroring server truth is a bug generator; the two drift and you find out in production.
Three parts
Split the component into useBatchSelection, useBatchSelectionSubmit and useBatchSelectionEventLogger, each with one job. Replace the legacy getBatchList thunk with RTK Query. Then move the rules to the API — and once the backend returns only the relevant batch, isLocked and isSingleBatchScenariodon’t need reimplementing, they need deleting.
The win was state removed, not state reorganised.
A useMemo doing nothing
I also took out a useMemo that was doing nothing: RTK Query already deep-equals its query arguments, so memoising them buys you a comparison you were getting for free.
The bug underneath
Then the thing worth the whole exercise. Rewriting the data layer surfaced that partner_slug and batch_code had never been reaching the API — customBaseQuery silently ignored RTK Query’s params field, so the partner flow had been fetching without its filters. Fixed by building the URL with URLSearchParams, matching the pattern already used elsewhere in the codebase.
What shipped with it
Unit tests, an axe-core pass, and role="list" added to satisfy aria-required-parent on the cards.