fix: LiveSearchForm — clear remet tout à zéro + focus préservé
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 47s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 47s
- Clear supprime le cookie ET force un remount du form (defaultValues à vide) - Navigation propre via useEffect au lieu de mutations pendant le render - Le focus est préservé lors de nos propres navigations (recherche, filtres) - Remount uniquement sur navigation externe (back/forward du navigateur) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef, useCallback, useEffect, useTransition } from "react";
|
import { useRef, useCallback, useEffect, useTransition, useState } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useTranslation } from "../../lib/i18n/context";
|
import { useTranslation } from "../../lib/i18n/context";
|
||||||
import { Icon } from "./ui";
|
import { Icon } from "./ui";
|
||||||
@@ -61,6 +61,9 @@ export function LiveSearchForm({ fields, basePath, debounceMs = 300 }: LiveSearc
|
|||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
// Incremented to force remount only on external navigation
|
||||||
|
const [formKey, setFormKey] = useState(0);
|
||||||
|
const lastNavParamsRef = useRef<string | null>(null);
|
||||||
|
|
||||||
const cookieName = filterCookieName(basePath);
|
const cookieName = filterCookieName(basePath);
|
||||||
|
|
||||||
@@ -93,20 +96,23 @@ export function LiveSearchForm({ fields, basePath, debounceMs = 300 }: LiveSearc
|
|||||||
} catch {}
|
} catch {}
|
||||||
}, [cookieName]);
|
}, [cookieName]);
|
||||||
|
|
||||||
|
const doNavigate = useCallback((url: string) => {
|
||||||
|
lastNavParamsRef.current = new URL(url, "http://x").search.replace(/^\?/, "");
|
||||||
|
startTransition(() => { router.replace(url as any); });
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
const navigate = useCallback((immediate: boolean) => {
|
const navigate = useCallback((immediate: boolean) => {
|
||||||
if (timerRef.current) clearTimeout(timerRef.current);
|
if (timerRef.current) clearTimeout(timerRef.current);
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
saveFilters();
|
saveFilters();
|
||||||
isOwnNavRef.current = true;
|
doNavigate(buildUrl());
|
||||||
startTransition(() => { router.replace(buildUrl() as any); });
|
|
||||||
} else {
|
} else {
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
saveFilters();
|
saveFilters();
|
||||||
isOwnNavRef.current = true;
|
doNavigate(buildUrl());
|
||||||
startTransition(() => { router.replace(buildUrl() as any); });
|
|
||||||
}, debounceMs);
|
}, debounceMs);
|
||||||
}
|
}
|
||||||
}, [router, buildUrl, debounceMs, saveFilters]);
|
}, [buildUrl, debounceMs, saveFilters, doNavigate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -114,6 +120,17 @@ export function LiveSearchForm({ fields, basePath, debounceMs = 300 }: LiveSearc
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Detect external navigation (back/forward) and remount form to sync values.
|
||||||
|
// Our own navigations store the expected params so we can skip the remount.
|
||||||
|
useEffect(() => {
|
||||||
|
const current = searchParams.toString();
|
||||||
|
if (lastNavParamsRef.current !== null && lastNavParamsRef.current !== current) {
|
||||||
|
// Params changed externally → remount to sync defaultValues
|
||||||
|
setFormKey(k => k + 1);
|
||||||
|
}
|
||||||
|
lastNavParamsRef.current = null;
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
const hasFilters = fields.some((f) => {
|
const hasFilters = fields.some((f) => {
|
||||||
const val = searchParams.get(f.name);
|
const val = searchParams.get(f.name);
|
||||||
return val && val.trim() !== "";
|
return val && val.trim() !== "";
|
||||||
@@ -122,32 +139,15 @@ export function LiveSearchForm({ fields, basePath, debounceMs = 300 }: LiveSearc
|
|||||||
const textFields = fields.filter((f) => f.type === "text");
|
const textFields = fields.filter((f) => f.type === "text");
|
||||||
const selectFields = fields.filter((f) => f.type === "select");
|
const selectFields = fields.filter((f) => f.type === "select");
|
||||||
|
|
||||||
// Track whether the current navigation was initiated by us (not back/forward)
|
|
||||||
const isOwnNavRef = useRef(false);
|
|
||||||
|
|
||||||
// Force remount only on external navigation (back/forward, cookie redirect)
|
|
||||||
// Our own navigations skip remount to preserve focus.
|
|
||||||
const prevParamsRef = useRef(searchParams.toString());
|
|
||||||
const formKey = useRef(0);
|
|
||||||
const currentParams = searchParams.toString();
|
|
||||||
if (currentParams !== prevParamsRef.current) {
|
|
||||||
if (!isOwnNavRef.current) {
|
|
||||||
formKey.current += 1; // External nav → remount
|
|
||||||
}
|
|
||||||
isOwnNavRef.current = false;
|
|
||||||
prevParamsRef.current = currentParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
key={formKey.current}
|
key={formKey}
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (timerRef.current) clearTimeout(timerRef.current);
|
if (timerRef.current) clearTimeout(timerRef.current);
|
||||||
saveFilters();
|
saveFilters();
|
||||||
isOwnNavRef.current = true;
|
doNavigate(buildUrl());
|
||||||
startTransition(() => { router.replace(buildUrl() as any); });
|
|
||||||
}}
|
}}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
@@ -206,8 +206,10 @@ export function LiveSearchForm({ fields, basePath, debounceMs = 300 }: LiveSearc
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
formRef.current?.reset();
|
formRef.current?.reset();
|
||||||
try { deleteCookie(cookieName); } catch {}
|
try { deleteCookie(cookieName); } catch {}
|
||||||
isOwnNavRef.current = true;
|
// Navigate to base path without any params
|
||||||
startTransition(() => { router.replace(basePath as any); });
|
doNavigate(basePath);
|
||||||
|
// Force remount so defaultValues reset to empty
|
||||||
|
setFormKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
className="
|
className="
|
||||||
inline-flex items-center gap-1
|
inline-flex items-center gap-1
|
||||||
|
|||||||
Reference in New Issue
Block a user