Basket persists across in-app navigation
useBasketUrlSync was treating every searchParams change as a URL→Store authority event. In practice this meant Back from PackagesStep to the providers map landed on `/` (no `?compare=...`) and the hook called setAll([]) — wiping the basket. Changed the semantics so that when an in-app navigation drops the `?compare=` param but the store still has items, we re-attach the store's keys to the new URL rather than clearing the store. Shared links still hydrate the store on initial mount, and the subscribe that writes store→URL on basket changes is untouched. With this, a user can: - Add a package on Provider A's page. - Back to the providers map (CompareBar stays, URL still shows `?compare=parsons:everyday`). - Navigate into Provider B's page (URL carries the Parsons item forward). - Add B's package (URL now `?compare=parsons:everyday,rankins:standard`). - Hit Compare with 2/3 basket. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,8 +18,14 @@ const deserialise = (raw: string | null): string[] =>
|
|||||||
*
|
*
|
||||||
* Mount once near the router root. URL is the source of truth on initial load
|
* Mount once near the router root. URL is the source of truth on initial load
|
||||||
* (so a shared link restores the basket); after that, store changes write
|
* (so a shared link restores the basket); after that, store changes write
|
||||||
* through to the URL and external URL changes (back/forward, manual edits)
|
* through to the URL so the current basket is always shareable.
|
||||||
* push back into the store.
|
*
|
||||||
|
* In-app navigation from a page that carries `?compare=...` to one that
|
||||||
|
* doesn't (e.g. Back from PackagesStep to the providers map) would drop the
|
||||||
|
* param — to avoid wiping the store, we re-attach the store's keys to the
|
||||||
|
* new URL instead of treating the empty URL as a "clear" signal. External
|
||||||
|
* URL changes that DO carry params still push back into the store (shared
|
||||||
|
* links, manual edits, browser Back after a store write).
|
||||||
*/
|
*/
|
||||||
export function useBasketUrlSync(): void {
|
export function useBasketUrlSync(): void {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -37,10 +43,27 @@ export function useBasketUrlSync(): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serialise(urlKeys) !== serialise(storeKeys)) {
|
if (serialise(urlKeys) === serialise(storeKeys)) return;
|
||||||
useComparisonBasket.getState().setAll(urlKeys);
|
|
||||||
|
// URL empty + store has items → in-app navigation dropped the param.
|
||||||
|
// Re-attach the store's keys so the basket stays sticky across routes
|
||||||
|
// (and the current URL remains shareable).
|
||||||
|
if (urlKeys.length === 0 && storeKeys.length > 0) {
|
||||||
|
setSearchParams(
|
||||||
|
(current) => {
|
||||||
|
const next = new URLSearchParams(current);
|
||||||
|
next.set(PARAM, serialise(storeKeys));
|
||||||
|
return next;
|
||||||
|
},
|
||||||
|
{ replace: true },
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
|
||||||
|
// Otherwise URL is authoritative (shared link, manual edit, browser Back
|
||||||
|
// after a store write) — push it into the store.
|
||||||
|
useComparisonBasket.getState().setAll(urlKeys);
|
||||||
|
}, [searchParams, setSearchParams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return useComparisonBasket.subscribe((state, prev) => {
|
return useComparisonBasket.subscribe((state, prev) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user