Document data router and unsaved changes usage

This commit is contained in:
Kari Lintulaakso
2025-12-17 14:49:33 +02:00
parent af33d7f97f
commit 26ce783859
2 changed files with 63 additions and 0 deletions
+4
View File
@@ -16,3 +16,7 @@ Ensure the "Return to table" button in detail views respects the originating Upd
- End-to-end flow: `npx cypress run --spec cypress/e2e/updateLogNavigation.cy.ts` (requires the application and database running locally).
Record any deviations in the QA log and link failures to the associated test artifacts.
## Routing & Unsaved Changes
For guidance on the data router setup and enabling unsaved-change prompts for new forms, see [`docs/routing.md`](./docs/routing.md).
+59
View File
@@ -0,0 +1,59 @@
# Routing & Unsaved Changes
This frontend uses React Router v6 in **data router mode** (`createBrowserRouter` + `RouterProvider`). This enables `useBlocker` and related APIs for navigation interception.
## Data Router Basics
- Entry point: `frontend/src/router/index.tsx` exports the router created with `createBrowserRouter` and is consumed by `RouterProvider` in `frontend/src/main.tsx`.
- Global layout: `App` renders global chrome and an `Outlet` for child routes.
- When adding routes, define them in `router/index.tsx` using the data-router configuration (array or `createRoutesFromElements`).
## Enabling Unsaved-Changes Prompts
Unsaved-change blocking is provided by `UnsavedChangesProvider` and the `useUnsavedChangesPrompt` hook.
**Provider**
- Wrap any page containing a form or editable detail view with `UnsavedChangesProvider`.
- The provider listens to `useBlocker` and renders the shared confirmation dialog.
**Hook**
- In a form component, call `const { setDirty } = useUnsavedChangesPrompt(isDirty)` where `isDirty` comes from your form state (`react-hook-form`’s `formState.isDirty`, etc.).
- Set dirty to `false` immediately before/after a successful submit so navigation to the next page isn’t blocked.
- If submit fails, set dirty back to `true` so protection remains while the user fixes errors.
### Minimal Example
```tsx
import { useForm } from 'react-hook-form';
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider';
import { useUnsavedChangesPrompt } from '@/hooks/useUnsavedChangesPrompt';
export const ExampleFormPage = () => {
const { handleSubmit, formState: { isDirty }, reset } = useForm();
const { setDirty } = useUnsavedChangesPrompt(isDirty);
const onSubmit = handleSubmit(async values => {
setDirty(false);
try {
await save(values);
reset(values);
} catch {
setDirty(true);
throw new Error('Save failed');
}
});
return (
<UnsavedChangesProvider>
<form onSubmit={onSubmit}>
{/* form fields */}
<button type="submit">Save</button>
</form>
</UnsavedChangesProvider>
);
};
```
### Detail Views
- For detail pages that use shared context (e.g., `DetailView`), place the provider at the page level and wire `useUnsavedChangesPrompt` to the context’s `isDirty` flag inside a small tracker component.
## Notes
- `useBlocker` requires the data router; ensure new routes continue to use `createBrowserRouter`.
- The dialog text can be customized via `UnsavedChangesProvider` props or by passing a `message` option to `useUnsavedChangesPrompt`.