-
Notifications
You must be signed in to change notification settings - Fork 20
[FRONTEND] Fix course creation form blocking navigation with false 'unsaved changes' warning #283
Description
Claiming This Task
Before you start working, check the Assignees section. If no one is assigned, leave a comment claiming the issue and assign it to yourself. This prevents duplicate work.
See the Community Wiki for contributing guidelines and git workflow.
Goal
Fix the course creation form so that after clicking "Create Course" and the API succeeds, the user is immediately redirected to the new course's detail page — without being blocked by the "You have unsaved changes" modal.
Bug Description
When a user fills out the course creation form and clicks "Create Course":
- The API call succeeds and the course is created
- The code calls
navigate(/courses/{id})to redirect to the course detail page - BUG: The
useBlockernavigation guard fires and shows the "Are you sure? You have unsaved changes" modal - The user has to click "Leave Without Saving" to actually get to their new course
This is a terrible UX — the user just saved their data by creating the course. There are no unsaved changes.
Root Cause
In CourseFormPage.tsx lines ~307-311, the submit handler does:
clearDraft();
setOriginalFormData(formData); // React state update — batched, not immediate
toast.success(t("course.form.createSuccess"));
navigate(`/courses/${newCourse.id}`, { replace: true }); // Runs before state flushessetOriginalFormData(formData) is meant to make isDirty become false so useBlocker won't trigger. But React batches state updates — the state hasn't flushed by the time navigate() runs synchronously on the next line. So useBlocker still sees isDirty = true and blocks the navigation.
Task Description
- Open
src/pages/CourseFormPage.tsx - Find the
doSubmit()function (around line 282) - In the
isNewCoursebranch, add a ref-based flag (e.g.isSubmittingRef) that is set totruebeforenavigate()is called - Update the
useBlockercallback to also check this flag — ifisSubmittingRef.currentistrue, don't block:
const isSubmittingRef = useRef(false);
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
isDirty &&
!isSubmittingRef.current &&
currentLocation.pathname !== nextLocation.pathname,
);- In
doSubmit(), set the ref before navigating:
if (isNewCourse) {
const newCourse = await createCourse(buildPayload());
clearDraft();
setOriginalFormData(formData);
isSubmittingRef.current = true; // Bypass blocker — data is saved
toast.success(t("course.form.createSuccess"));
navigate(`/courses/${newCourse.id}`, { replace: true });
}- The ref approach works because refs update synchronously (unlike state), so
useBlockersees the updated value immediately.
Definition of Done
- After creating a new course, the user is immediately redirected to
/courses/{id}with no modal - The "unsaved changes" modal still works correctly for actual unsaved changes (e.g. clicking browser back while editing)
- The
beforeunloadbrowser warning still works during editing - Edit mode still works correctly (no redirect, dirty state resets properly)
- All existing tests pass
- No TypeScript or lint errors
Benefits
- Fixes a frustrating UX bug that makes course creation feel broken
- Teaches the contributor about React state batching, refs vs state, and navigation guards
Skills You'll Practice
- React refs (
useRef) vs state (useState) — understanding synchronous vs batched updates - React Router
useBlockerAPI - Debugging race conditions in React
- Working with form submission flows
Files to be Altered (if known)
src/pages/CourseFormPage.tsx— add ref flag and updateuseBlocker+doSubmit()
Testing
- Create a new course: Fill out the form → click Create → should redirect straight to the course detail page, no modal
- Edit an existing course: Change a field → click browser back → should show "unsaved changes" modal
- Navigate away during creation: Fill out the form → click a navbar link without submitting → should show "unsaved changes" modal
- Close tab during creation: Fill out the form → try to close the browser tab → should see browser's native "Leave site?" warning
- Run
npx tsc --noEmit,npm run lint,npx vitest run— all should pass
Tips (Optional)
- The key insight is that
useRefupdates are synchronous whileuseStateupdates are batched by React. When you need a value to be readable immediately in the same render cycle (likeuseBlocker's callback), use a ref. - Don't remove
setOriginalFormData(formData)— it's still needed to reset dirty state for the edit mode flow. The ref is an additional safeguard for the create+navigate flow. - The fix is small (< 5 lines changed) but the understanding behind it is valuable.
Getting started? These wiki pages will help you get set up:
This is a beginner-friendly task. Ask questions in Discord — we're here to help!