Migrating from 3.13.x to 4.0.0

Five small breaking changes — all aliased and dev-warned in 3.13.x. If your 3.13.x build runs without warnings in the dev console, your 4.0 upgrade is a one-line bump.

TL;DR

4.0 is the consolidated breaking-change window. Every removed alias or behavioural change was deprecated and dev-warned in 3.13.x, so the upgrade path is:

  1. On 3.13.x, run your dev server. Any deprecated prop or behaviour fires a one-shot [ndpr-toolkit/<module>] warning in the console.
  2. Fix each warning at its callsite.
  3. Bump to ^4.0.0.
pnpm up @tantainnovative/ndpr-toolkit@^4.0.0
# or
bun add @tantainnovative/ndpr-toolkit@^4.0.0
# or
npm install @tantainnovative/ndpr-toolkit@^4.0.0

The breaking changes

1. React 17 dropped from peerDependencies

The peer range is now ^18.0.0 || ^19.0.0. The toolkit uses React.useId (18+) in several components, so the previous ^16.8 || ^17 || ^18 || ^19 claim was already a lie — React 17 consumers installed fine then hit cryptic errors. This release just makes the peer range honest.

Action: if you're on React 17 (or earlier), upgrade your app to React 18+ before bumping. If you're on React 18 or 19, no action required.

2. formDescriptiondescription

BreachReportForm and the NDPRBreachReport preset have a description prop matching every other component. The legacy formDescription alias is removed.

// Before (3.x)
<BreachReportForm formDescription="Custom blurb" />
<NDPRBreachReport formDescription="Custom blurb" />

// After (4.0)
<BreachReportForm description="Custom blurb" />
<NDPRBreachReport description="Custom blurb" />

3. initialActivities / initialTransfersinitialData

NDPRLawfulBasis and NDPRCrossBorder presets standardised on initialData (matches NDPRROPA). The module-specific aliases are removed.

// Before (3.x)
<NDPRLawfulBasis initialActivities={activities} />
<NDPRCrossBorder initialTransfers={transfers} />

// After (4.0)
<NDPRLawfulBasis initialData={activities} />
<NDPRCrossBorder initialData={transfers} />

4. extraOptions removed from NDPRConsent

The one-off extraOptions pattern (which extended the toolkit's defaults) is gone. Pass the full options array via options instead.

// Before (3.x)
<NDPRConsent extraOptions={[{ id: 'research', label: 'Research', ... }]} />

// After (4.0)
<NDPRConsent
  options={[
    { id: 'essential', label: 'Essential', description: 'Required', required: true, purpose: 'Site operation' },
    { id: 'analytics', label: 'Analytics', description: 'Usage measurement', required: false, purpose: 'Product analytics' },
    { id: 'research', label: 'Research', description: 'For research', required: false, purpose: 'Research' },
  ]}
/>

5. useDSR default storage flipped to in-memory

useDSR is the admin tracker hook — its state holds data subjects' PII. The previous default stored that PII in the admin's browser localStorage, which is rarely appropriate. 4.0 flips the default to in-memory; localStorage requires explicit opt-in.

// Before (3.x): default useLocalStorage was true
const { requests } = useDSR({ requestTypes });
// → requests persisted to localStorage["ndpr_dsr_requests"]

// After (4.0): default useLocalStorage is false
const { requests } = useDSR({ requestTypes });
// → requests live in component state only; nothing persists

// To keep the old behaviour (rarely the right move):
const { requests } = useDSR({ requestTypes, useLocalStorage: true });

// Recommended: pass an explicit adapter for production
const { requests } = useDSR({
  requestTypes,
  adapter: apiAdapter('/api/dsr'),
});

6. Example rename: dsr-backend-proddsr-backend-reference

The example was renamed to be honest about its scope. The "prod" name oversold what it does — it covers DSR receipt and confirmation only, not the full fulfilment pipeline (identity verification, audit logging, status transitions, request-type-specific handling, deadline tracking, DPO routing). The example's README now ships an explicit "What you still need to build" checklist.

Action: if you bookmarked or cloned examples/dsr-backend-prod/, update to examples/dsr-backend-reference/.

Everything else stays the same

  • All 19 component *Props interfaces stay exported from the root.
  • Adapter contract (StorageAdapter<T>, localStorageAdapter, cookieAdapter, apiAdapter, memoryAdapter, composeAdapters) — unchanged.
  • The /server, /headless, /hooks, /presets, and /unstyled subpaths — unchanged.
  • Compliance score engine, NDPA validators, all 5 locale files, the NDPRThemeProvider — unchanged.
  • i18n wiring across all 20 components (shipped in 3.13.0) — stays in place.

After the bump — verify checklist

  1. tsc --noEmit — no new type errors. If you hit any, they're almost certainly one of the 5 renames above.
  2. Run the app in dev. No more [ndpr-toolkit/...] deprecation warnings.
  3. If you use useDSR() without an explicit adapter, the tracker now starts empty on every reload — that's intentional. Pass apiAdapter or useLocalStorage: true if you need persistence.