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
useBlocker navigation 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 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
- Open
src/pages/CourseFormPage.tsx
- Find the
doSubmit() function (around line 282)
- In the
isNewCourse branch, add a ref-based flag (e.g. isSubmittingRef) that is set to true before navigate() is called
- 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,
);
- 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
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
- 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
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!
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":
navigate(/courses/{id})to redirect to the course detail pageuseBlockernavigation guard fires and shows the "Are you sure? You have unsaved changes" modalThis is a terrible UX — the user just saved their data by creating the course. There are no unsaved changes.
Root Cause
In
CourseFormPage.tsxlines ~307-311, the submit handler does:setOriginalFormData(formData)is meant to makeisDirtybecomefalsesouseBlockerwon't trigger. But React batches state updates — the state hasn't flushed by the timenavigate()runs synchronously on the next line. SouseBlockerstill seesisDirty = trueand blocks the navigation.Task Description
src/pages/CourseFormPage.tsxdoSubmit()function (around line 282)isNewCoursebranch, add a ref-based flag (e.g.isSubmittingRef) that is set totruebeforenavigate()is calleduseBlockercallback to also check this flag — ifisSubmittingRef.currentistrue, don't block:doSubmit(), set the ref before navigating:useBlockersees the updated value immediately.Definition of Done
/courses/{id}with no modalbeforeunloadbrowser warning still works during editingBenefits
Skills You'll Practice
useRef) vs state (useState) — understanding synchronous vs batched updatesuseBlockerAPIFiles to be Altered (if known)
src/pages/CourseFormPage.tsx— add ref flag and updateuseBlocker+doSubmit()Testing
npx tsc --noEmit,npm run lint,npx vitest run— all should passTips (Optional)
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.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.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!