@1money/hooks 0.1.2 → 0.1.3

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.
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  /**
2
3
  * A hook that returns a ref object whose `.current` property is always
3
4
  * updated to the latest value. Useful for accessing the latest value
@@ -42,13 +42,24 @@ export declare const queryJson: Parser<unknown>;
42
42
  * parameter, so it survives refreshes and is
43
43
  * shareable via the link.
44
44
  *
45
- * Reads/writes go through native
46
- * `history.pushState/replaceState`, which keeps it
47
- * compatible with the Next.js App Router (it does
48
- * not trigger a route re-mount). Because the value
49
- * is reactive, using it as a SWR/React Query key
45
+ * Reading is reactive via Next's `useSearchParams`;
46
+ * writing goes through the native
47
+ * `history.pushState/replaceState`, which the Next.js
48
+ * App Router intercepts and syncs back into
49
+ * `useSearchParams` *without* a server round-trip (no
50
+ * route re-render, no scroll-to-top). Because the
51
+ * value is reactive, using it as a SWR/React Query key
50
52
  * makes dependent requests refetch automatically.
51
53
  *
54
+ * Constraints (inherited from `useSearchParams`):
55
+ * - Next.js App Router only; the calling component
56
+ * must be a Client Component (`'use client'`).
57
+ * - On statically rendered routes, wrap the consumer
58
+ * in `<Suspense>`; otherwise that subtree opts into
59
+ * client-side rendering. During prerender the param
60
+ * reads as absent, so `value` falls back to
61
+ * `defaultValue ?? null`.
62
+ *
52
63
  * @param key The query parameter name
53
64
  * @param options Parsing, serialization and history
54
65
  * behaviour
@@ -1,10 +1,6 @@
1
- function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
- function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
5
- function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
- function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
- import { useState, useEffect } from 'react';
1
+ 'use client';
2
+
3
+ import { useSearchParams } from 'next/navigation';
8
4
  import useMemoizedFn from "../useMemoizedFn";
9
5
 
10
6
  /** Turn a raw query string into a typed value. */
@@ -32,30 +28,29 @@ export var queryJson = function queryJson(value) {
32
28
  };
33
29
  var isBrowser = typeof window !== 'undefined';
34
30
 
35
- /**
36
- * Subscribers re-read the URL when any instance
37
- * mutates it. `history.pushState/replaceState` do
38
- * not emit `popstate`, so we notify manually.
39
- */
40
- var listeners = new Set();
41
- var emit = function emit() {
42
- listeners.forEach(function (fn) {
43
- return fn();
44
- });
45
- };
46
-
47
31
  /**
48
32
  * Sync a piece of React state with a URL query
49
33
  * parameter, so it survives refreshes and is
50
34
  * shareable via the link.
51
35
  *
52
- * Reads/writes go through native
53
- * `history.pushState/replaceState`, which keeps it
54
- * compatible with the Next.js App Router (it does
55
- * not trigger a route re-mount). Because the value
56
- * is reactive, using it as a SWR/React Query key
36
+ * Reading is reactive via Next's `useSearchParams`;
37
+ * writing goes through the native
38
+ * `history.pushState/replaceState`, which the Next.js
39
+ * App Router intercepts and syncs back into
40
+ * `useSearchParams` *without* a server round-trip (no
41
+ * route re-render, no scroll-to-top). Because the
42
+ * value is reactive, using it as a SWR/React Query key
57
43
  * makes dependent requests refetch automatically.
58
44
  *
45
+ * Constraints (inherited from `useSearchParams`):
46
+ * - Next.js App Router only; the calling component
47
+ * must be a Client Component (`'use client'`).
48
+ * - On statically rendered routes, wrap the consumer
49
+ * in `<Suspense>`; otherwise that subtree opts into
50
+ * client-side rendering. During prerender the param
51
+ * reads as absent, so `value` falls back to
52
+ * `defaultValue ?? null`.
53
+ *
59
54
  * @param key The query parameter name
60
55
  * @param options Parsing, serialization and history
61
56
  * behaviour
@@ -77,43 +72,26 @@ function useQueryState(key) {
77
72
  defaultValue = options.defaultValue,
78
73
  _options$history = options.history,
79
74
  history = _options$history === void 0 ? 'replace' : _options$history;
80
- var read = useMemoizedFn(function () {
81
- if (!isBrowser) {
82
- return defaultValue !== null && defaultValue !== void 0 ? defaultValue : null;
83
- }
84
- var params = new URLSearchParams(window.location.search);
85
- var raw = params.get(key);
75
+ var parse = function parse(raw) {
86
76
  if (raw === null) {
87
77
  return defaultValue !== null && defaultValue !== void 0 ? defaultValue : null;
88
78
  }
89
79
  return parser ? parser(raw) : raw;
90
- });
91
- var _useState = useState(read),
92
- _useState2 = _slicedToArray(_useState, 2),
93
- value = _useState2[0],
94
- setValue = _useState2[1];
95
- useEffect(function () {
96
- var handler = function handler() {
97
- return setValue(read());
98
- };
99
- listeners.add(handler);
100
- window.addEventListener('popstate', handler);
101
- // Resync in case the URL changed between the
102
- // initial render and this effect running.
103
- handler();
104
- return function () {
105
- listeners.delete(handler);
106
- window.removeEventListener('popstate', handler);
107
- };
108
- // `read` is stable via useMemoizedFn.
109
- }, [key, read]);
80
+ };
81
+
82
+ // Reactive read. Next re-renders consumers when the
83
+ // URL changes (including our own history writes).
84
+ var searchParams = useSearchParams();
85
+ var value = parse(searchParams.get(key));
110
86
  var set = useMemoizedFn(function (next) {
111
87
  if (!isBrowser) {
112
88
  return;
113
89
  }
114
- var prev = read();
115
- var resolved = typeof next === 'function' ? next(prev) : next;
90
+ // Build from the live URL (not the captured
91
+ // `searchParams`) so consecutive writes in one
92
+ // tick see each other's result.
116
93
  var params = new URLSearchParams(window.location.search);
94
+ var resolved = typeof next === 'function' ? next(parse(params.get(key))) : next;
117
95
  if (resolved === null || resolved === undefined) {
118
96
  params.delete(key);
119
97
  } else {
@@ -126,7 +104,6 @@ function useQueryState(key) {
126
104
  } else {
127
105
  window.history.replaceState(null, '', url);
128
106
  }
129
- emit();
130
107
  });
131
108
  return [value, set];
132
109
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  /**
2
3
  * A hook that returns a ref object whose `.current` property is always
3
4
  * updated to the latest value. Useful for accessing the latest value
@@ -42,13 +42,24 @@ export declare const queryJson: Parser<unknown>;
42
42
  * parameter, so it survives refreshes and is
43
43
  * shareable via the link.
44
44
  *
45
- * Reads/writes go through native
46
- * `history.pushState/replaceState`, which keeps it
47
- * compatible with the Next.js App Router (it does
48
- * not trigger a route re-mount). Because the value
49
- * is reactive, using it as a SWR/React Query key
45
+ * Reading is reactive via Next's `useSearchParams`;
46
+ * writing goes through the native
47
+ * `history.pushState/replaceState`, which the Next.js
48
+ * App Router intercepts and syncs back into
49
+ * `useSearchParams` *without* a server round-trip (no
50
+ * route re-render, no scroll-to-top). Because the
51
+ * value is reactive, using it as a SWR/React Query key
50
52
  * makes dependent requests refetch automatically.
51
53
  *
54
+ * Constraints (inherited from `useSearchParams`):
55
+ * - Next.js App Router only; the calling component
56
+ * must be a Client Component (`'use client'`).
57
+ * - On statically rendered routes, wrap the consumer
58
+ * in `<Suspense>`; otherwise that subtree opts into
59
+ * client-side rendering. During prerender the param
60
+ * reads as absent, so `value` falls back to
61
+ * `defaultValue ?? null`.
62
+ *
52
63
  * @param key The query parameter name
53
64
  * @param options Parsing, serialization and history
54
65
  * behaviour
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -36,17 +37,13 @@ __export(index_exports, {
36
37
  queryString: () => queryString
37
38
  });
38
39
  module.exports = __toCommonJS(index_exports);
39
- var import_react = require("react");
40
+ var import_navigation = require("next/navigation");
40
41
  var import_useMemoizedFn = __toESM(require("../useMemoizedFn"));
41
42
  var queryString = (value) => value;
42
43
  var queryNumber = (value) => Number(value);
43
44
  var queryBoolean = (value) => value === "true";
44
45
  var queryJson = (value) => JSON.parse(value);
45
46
  var isBrowser = typeof window !== "undefined";
46
- var listeners = /* @__PURE__ */ new Set();
47
- var emit = () => {
48
- listeners.forEach((fn) => fn());
49
- };
50
47
  function useQueryState(key, options = {}) {
51
48
  const {
52
49
  parser,
@@ -54,42 +51,24 @@ function useQueryState(key, options = {}) {
54
51
  defaultValue,
55
52
  history = "replace"
56
53
  } = options;
57
- const read = (0, import_useMemoizedFn.default)(() => {
58
- if (!isBrowser) {
59
- return defaultValue ?? null;
60
- }
61
- const params = new URLSearchParams(
62
- window.location.search
63
- );
64
- const raw = params.get(key);
54
+ const parse = (raw) => {
65
55
  if (raw === null) {
66
56
  return defaultValue ?? null;
67
57
  }
68
58
  return parser ? parser(raw) : raw;
69
- });
70
- const [value, setValue] = (0, import_react.useState)(read);
71
- (0, import_react.useEffect)(() => {
72
- const handler = () => setValue(read());
73
- listeners.add(handler);
74
- window.addEventListener("popstate", handler);
75
- handler();
76
- return () => {
77
- listeners.delete(handler);
78
- window.removeEventListener(
79
- "popstate",
80
- handler
81
- );
82
- };
83
- }, [key, read]);
59
+ };
60
+ const searchParams = (0, import_navigation.useSearchParams)();
61
+ const value = parse(searchParams.get(key));
84
62
  const set = (0, import_useMemoizedFn.default)((next) => {
85
63
  if (!isBrowser) {
86
64
  return;
87
65
  }
88
- const prev = read();
89
- const resolved = typeof next === "function" ? next(prev) : next;
90
66
  const params = new URLSearchParams(
91
67
  window.location.search
92
68
  );
69
+ const resolved = typeof next === "function" ? next(
70
+ parse(params.get(key))
71
+ ) : next;
93
72
  if (resolved === null || resolved === void 0) {
94
73
  params.delete(key);
95
74
  } else {
@@ -102,7 +81,6 @@ function useQueryState(key, options = {}) {
102
81
  } else {
103
82
  window.history.replaceState(null, "", url);
104
83
  }
105
- emit();
106
84
  });
107
85
  return [value, set];
108
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1money/hooks",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "React hooks for 1money front-end projects",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -90,8 +90,14 @@
90
90
  "url": "https://github.com/1Money-Co/1money-hooks"
91
91
  },
92
92
  "peerDependencies": {
93
+ "next": ">=14.0.0",
93
94
  "react": ">=16.8.0"
94
95
  },
96
+ "peerDependenciesMeta": {
97
+ "next": {
98
+ "optional": true
99
+ }
100
+ },
95
101
  "license": "MIT",
96
102
  "devDependencies": {
97
103
  "@eslint/js": "^9.39.4",
@@ -108,6 +114,7 @@
108
114
  "father": "^4.6.17",
109
115
  "globals": "^15.15.0",
110
116
  "jsdom": "^29.0.2",
117
+ "next": "^14.2.0",
111
118
  "prettier": "~3.5.3",
112
119
  "react": "^19.1.0",
113
120
  "react-dom": "^19.2.4",