clear-react-router 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -272,6 +272,53 @@ const { setContext, context } = useRouterContext();
272
272
  const loginHandler = () => setContext({ ...context, user: { name: 'John' } });
273
273
  ```
274
274
 
275
+ ### `useSearchParams()`
276
+
277
+ Returns an object for working with URL query parameters. Supports reading and setting both single values and arrays.
278
+
279
+ ```
280
+ import { useSearchParams } from 'clear-react-router';
281
+
282
+ function ProductFilter() {
283
+ const { searchParams, getSearchParams, setSearchParams } = useSearchParams();
284
+
285
+ // Get a single value or array
286
+ const brand = getSearchParams('brand'); // 'nike' | ['nike', 'reebok'] | ''
287
+
288
+ // Set a single value
289
+ setSearchParams('brand', 'nike'); // ?brand=nike
290
+
291
+ // Set multiple values (array)
292
+ setSearchParams('brand', ['nike', 'reebok']); // ?brand=nike&brand=reebok
293
+
294
+ // Functional update (preserves other params)
295
+ setSearchParams((prev) => {
296
+ prev.set('page', '2');
297
+ prev.append('color', 'red');
298
+ return prev;
299
+ });
300
+
301
+ // Direct access to URLSearchParams
302
+ const allParams = searchParams.toString(); // "brand=nike&brand=reebok&page=2"
303
+ }
304
+ ```
305
+ **Returns:**
306
+
307
+ | Property | Type | Description |
308
+ |----------|------|-------------|
309
+ | `searchParams` | `URLSearchParams` | Raw `URLSearchParams` object for low-level access |
310
+ | `getSearchParams` | `(key: string) => string \| string[]` | Returns a single value or an array if multiple values exist for the key |
311
+ | `setSearchParams` | `(param: string, value: string \| string[]) => void` `or` `(updater: (prev: URLSearchParams) => URLSearchParams) => void` | Update query parameters. Supports single values, arrays, or functional updates |
312
+
313
+ **Key features:**
314
+
315
+ - ✅ **Array support** — `getSearchParams` returns `string[]` when multiple values exist for the same key
316
+ - ✅ **Functional updates** — Update parameters based on previous state without losing other params
317
+ - ✅ **Type-safe** — Proper TypeScript support with overloads
318
+ - ✅ **Stable reference** — `setSearchParams` reference is stable and safe to use in `useEffect`
319
+
320
+ > **Note:** `getSearchParams` returns `string` for single values, `string[]` for multiple values, and `''` if the key is not found.
321
+
275
322
  ## Lazy Loading
276
323
 
277
324
  Clear Router supports code-splitting out of the box. Simply pass a function that returns a dynamic import:
@@ -0,0 +1,10 @@
1
+ type UseSearchParamsReturn = {
2
+ searchParams: URLSearchParams;
3
+ getSearchParams(arg: string): string | string[];
4
+ setSearchParams: {
5
+ (param: string, value: string | string[]): void;
6
+ (param: (prevState: URLSearchParams) => URLSearchParams): void;
7
+ };
8
+ };
9
+ export declare const useSearchParams: () => UseSearchParamsReturn;
10
+ export {};
package/dist/index.d.ts CHANGED
@@ -8,5 +8,6 @@ export { useLoaderState } from './hooks/useLoaderState';
8
8
  export { useBlocker } from './hooks/useBlocker';
9
9
  export { useBeforeUnload } from './hooks/useBeforeUnload';
10
10
  export { useRouterContext } from './hooks/useRouterContext';
11
+ export { useSearchParams } from './hooks/useSearchParams';
11
12
  export { createRouter } from './utils/utils';
12
13
  export type { RouteItem, BlockerState, Location } from './types/global';
package/dist/index.js CHANGED
@@ -149,8 +149,9 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
149
149
  const navigation = useCallback((nextLocation) => {
150
150
  setLocation(nextLocation);
151
151
  prevPathname.current = nextLocation.pathname;
152
- if (nextLocation.pathname === window.location.pathname) return;
153
- history.pushState(null, "", nextLocation.pathname);
152
+ const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
153
+ if (fullPath === window.location.pathname + window.location.search) return;
154
+ history.pushState(null, "", fullPath);
154
155
  }, [setLocation]);
155
156
  const transitionedNavigation = useCallback(({ nextLocation, isFirstCall, isAnimated }) => {
156
157
  if (isAnimated && !isFirstCall) try {
@@ -427,26 +428,15 @@ var renderElement = (Component) => {
427
428
  };
428
429
  //#endregion
429
430
  //#region hooks/useServiceContext.ts
430
- var useNavigationState = () => {
431
- const context = useContext(NavigationContext);
432
- if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
433
- return context;
434
- };
435
- var useRouterActions = () => {
436
- const context = useContext(ActionsContext);
437
- if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
438
- return context;
439
- };
440
- var useRouterData = () => {
441
- const context = useContext(DataContext);
442
- if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
443
- return context;
444
- };
445
- var usePropsData = () => {
446
- const context = useContext(PropsContext);
431
+ var useServiceState = (reactContext) => {
432
+ const context = useContext(reactContext);
447
433
  if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
448
434
  return context;
449
435
  };
436
+ var useNavigationState = () => useServiceState(NavigationContext);
437
+ var useRouterActions = () => useServiceState(ActionsContext);
438
+ var useRouterData = () => useServiceState(DataContext);
439
+ var usePropsData = () => useServiceState(PropsContext);
450
440
  //#endregion
451
441
  //#region context/RouterViewContext.ts
452
442
  var RouterViewContext = createContext({});
@@ -501,13 +491,13 @@ var useLocation = () => {
501
491
  //#region hooks/useNavigate.ts
502
492
  var useNavigate = () => {
503
493
  const { updateLocation } = useRouterActions();
504
- const location = useLocation();
494
+ const locationRef = useLatest(useLocation());
505
495
  return useCallback(async (arg) => {
506
496
  if (typeof arg === "number") return history.go(arg);
507
497
  if (typeof arg === "string") {
508
- if (arg !== location.pathname) await updateLocation({ pathname: arg });
509
- } else if (JSON.stringify(arg) !== JSON.stringify(location)) await updateLocation(arg);
510
- }, [location, updateLocation]);
498
+ if (arg !== locationRef.current.pathname) await updateLocation({ pathname: arg });
499
+ } else if (JSON.stringify(arg) !== JSON.stringify(locationRef.current)) await updateLocation(arg);
500
+ }, [updateLocation, locationRef]);
511
501
  };
512
502
  //#endregion
513
503
  //#region components/Link.tsx
@@ -576,4 +566,38 @@ var useRouterContext = () => {
576
566
  };
577
567
  };
578
568
  //#endregion
579
- export { Link, Router, RouterProvider, createRouter, useBeforeUnload, useBlocker, useLoaderState, useLocation, useNavigate, useParams, useRouterContext };
569
+ //#region hooks/useSearchParams.ts
570
+ var useSearchParams = () => {
571
+ const location = useLocation();
572
+ const navigate = useNavigate();
573
+ const locationRef = useLatest(location);
574
+ const searchString = useMemo(() => location.search ? location.search.replace("?", "") : location.pathname.split("?")?.[1] ?? "", [location.pathname, location.search]);
575
+ const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]);
576
+ const getSearchParams = useCallback((param) => {
577
+ const allValues = searchParams.getAll(param);
578
+ return allValues.length > 1 ? allValues : allValues[0] ?? "";
579
+ }, [searchParams]);
580
+ const navigateWithSearchParams = useCallback((params) => {
581
+ const newSearch = params.toString();
582
+ navigate({
583
+ pathname: locationRef.current.pathname,
584
+ search: newSearch ? `?${newSearch}` : ""
585
+ });
586
+ }, [locationRef, navigate]);
587
+ return {
588
+ searchParams,
589
+ getSearchParams,
590
+ setSearchParams: useCallback((param, value) => {
591
+ const search = locationRef.current.search || "";
592
+ const currentParams = new URLSearchParams(search);
593
+ if (typeof param === "string" && value !== void 0) {
594
+ currentParams.delete(param);
595
+ (Array.isArray(value) ? value : [value]).forEach((v) => currentParams.append(param, v));
596
+ navigateWithSearchParams(currentParams);
597
+ } else if (typeof param === "function") navigateWithSearchParams(param(currentParams));
598
+ else throw new Error("useSearchParams first argument must be either function or string");
599
+ }, [locationRef, navigateWithSearchParams])
600
+ };
601
+ };
602
+ //#endregion
603
+ export { Link, Router, RouterProvider, createRouter, useBeforeUnload, useBlocker, useLoaderState, useLocation, useNavigate, useParams, useRouterContext, useSearchParams };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {