Skip to content

[FRONTEND] Fix course creation form blocking navigation with false 'unsaved changes' warning #283

@smattymatty

Description

@smattymatty

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":

  1. The API call succeeds and the course is created
  2. The code calls navigate(/courses/{id}) to redirect to the course detail page
  3. BUG: The useBlocker navigation guard fires and shows the "Are you sure? You have unsaved changes" modal
  4. 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 flushes

setOriginalFormData(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

  1. Open src/pages/CourseFormPage.tsx
  2. Find the doSubmit() function (around line 282)
  3. In the isNewCourse branch, add a ref-based flag (e.g. isSubmittingRef) that is set to true before navigate() is called
  4. Update the useBlocker callback to also check this flag — if isSubmittingRef.current is true, don't block:
const isSubmittingRef = useRef(false);

const blocker = useBlocker(
  ({ currentLocation, nextLocation }) =>
    isDirty &&
    !isSubmittingRef.current &&
    currentLocation.pathname !== nextLocation.pathname,
);
  1. 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 });
}
  1. The ref approach works because refs update synchronously (unlike state), so useBlocker sees 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 beforeunload browser 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 useBlocker API
  • Debugging race conditions in React
  • Working with form submission flows

Files to be Altered (if known)

  • src/pages/CourseFormPage.tsx — add ref flag and update useBlocker + doSubmit()

Testing

  1. Create a new course: Fill out the form → click Create → should redirect straight to the course detail page, no modal
  2. Edit an existing course: Change a field → click browser back → should show "unsaved changes" modal
  3. Navigate away during creation: Fill out the form → click a navbar link without submitting → should show "unsaved changes" modal
  4. Close tab during creation: Fill out the form → try to close the browser tab → should see browser's native "Leave site?" warning
  5. Run npx tsc --noEmit, npm run lint, npx vitest run — all should pass

Tips (Optional)

  • The key insight is that useRef updates are synchronous while useState updates are batched by React. When you need a value to be readable immediately in the same render cycle (like useBlocker'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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions