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). - 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. 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`.