@graphcommerce/react-hook-form 7.0.0-canary.21 → 7.0.1-canary.0

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/CHANGELOG.md CHANGED
@@ -1,6 +1,194 @@
1
1
  # Change Log
2
2
 
3
- ## 7.0.0-canary.21
3
+ ## 7.0.1-canary.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2047](https://github.com/graphcommerce-org/graphcommerce/pull/2047) [`136580b39`](https://github.com/graphcommerce-org/graphcommerce/commit/136580b39e3cffdd07e3fa087e049bd532c3e8f1) - Updated all dependencies to the latest version where possible. ([@paales](https://github.com/paales))
8
+
9
+ ## 7.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - [`e55d8c390`](https://github.com/graphcommerce-org/graphcommerce/commit/e55d8c390d90b4bb7bab11c6a99027ac72bd7e3e) - Created a new sidebar layout system, can be configured with productFiltersLayout in the graphcommerce.config.js ([@paales](https://github.com/paales))
14
+
15
+ ### Patch Changes
16
+
17
+ - [#1960](https://github.com/graphcommerce-org/graphcommerce/pull/1960) [`f78caf5a8`](https://github.com/graphcommerce-org/graphcommerce/commit/f78caf5a83683f1ae4b901fb94bd22d50943fa2f) - Updated packages: `next`, `@apollo/client`, `react-hook-form`, `@emotion/*`, `@lingui/*`, `@mui/*` and various others. ([@paales](https://github.com/paales))
18
+
19
+ - [#2012](https://github.com/graphcommerce-org/graphcommerce/pull/2012) [`1dbb3ae13`](https://github.com/graphcommerce-org/graphcommerce/commit/1dbb3ae13553992ee1ed77f375375560f28c418c) - Upgrade graphql to 16.7.1, add graphql as peer dependency ([@Giovanni-Schroevers](https://github.com/Giovanni-Schroevers))
20
+
21
+ ## 6.2.0-canary.98
22
+
23
+ ## 6.2.0-canary.97
24
+
25
+ ## 6.2.0-canary.96
26
+
27
+ ## 6.2.0-canary.95
28
+
29
+ ## 6.2.0-canary.94
30
+
31
+ ## 6.2.0-canary.93
32
+
33
+ ## 6.2.0-canary.92
34
+
35
+ ## 6.2.0-canary.91
36
+
37
+ ## 6.2.0-canary.90
38
+
39
+ ## 6.2.0-canary.89
40
+
41
+ ## 6.2.0-canary.88
42
+
43
+ ## 6.2.0-canary.87
44
+
45
+ ## 6.2.0-canary.86
46
+
47
+ ## 6.2.0-canary.85
48
+
49
+ ## 6.2.0-canary.84
50
+
51
+ ## 6.2.0-canary.83
52
+
53
+ ## 6.2.0-canary.82
54
+
55
+ ## 6.2.0-canary.81
56
+
57
+ ## 6.2.0-canary.80
58
+
59
+ ## 6.2.0-canary.79
60
+
61
+ ## 6.2.0-canary.78
62
+
63
+ ## 6.2.0-canary.77
64
+
65
+ ## 6.2.0-canary.76
66
+
67
+ ## 6.2.0-canary.75
68
+
69
+ ## 6.2.0-canary.74
70
+
71
+ ## 6.2.0-canary.73
72
+
73
+ ## 6.2.0-canary.72
74
+
75
+ ## 6.2.0-canary.71
76
+
77
+ ## 6.2.0-canary.70
78
+
79
+ ## 6.2.0-canary.69
80
+
81
+ ### Patch Changes
82
+
83
+ - [#2012](https://github.com/graphcommerce-org/graphcommerce/pull/2012) [`1dbb3ae13`](https://github.com/graphcommerce-org/graphcommerce/commit/1dbb3ae13553992ee1ed77f375375560f28c418c) - Upgrade graphql to 16.7.1, add graphql as peer dependency ([@Giovanni-Schroevers](https://github.com/Giovanni-Schroevers))
84
+
85
+ ## 6.2.0-canary.68
86
+
87
+ ## 6.2.0-canary.67
88
+
89
+ ### Patch Changes
90
+
91
+ - [#2002](https://github.com/graphcommerce-org/graphcommerce/pull/2002) [`1234bb61f`](https://github.com/graphcommerce-org/graphcommerce/commit/1234bb61f8332da8a9e4dd7262b0c70beaed8c91) - Updated next and apollo/client ([@paales](https://github.com/paales))
92
+
93
+ ## 6.2.0-canary.66
94
+
95
+ ## 6.2.0-canary.65
96
+
97
+ ## 6.2.0-canary.64
98
+
99
+ ## 6.2.0-canary.63
100
+
101
+ ## 6.2.0-canary.62
102
+
103
+ ## 6.2.0-canary.61
104
+
105
+ ## 6.2.0-canary.60
106
+
107
+ ## 6.2.0-canary.59
108
+
109
+ ## 6.2.0-canary.58
110
+
111
+ ## 6.2.0-canary.57
112
+
113
+ ## 6.2.0-canary.56
114
+
115
+ ## 6.2.0-canary.55
116
+
117
+ ## 6.2.0-canary.54
118
+
119
+ ## 6.2.0-canary.53
120
+
121
+ ## 6.2.0-canary.52
122
+
123
+ ## 6.2.0-canary.51
124
+
125
+ ## 6.2.0-canary.50
126
+
127
+ ### Minor Changes
128
+
129
+ - [`e55d8c390`](https://github.com/graphcommerce-org/graphcommerce/commit/e55d8c390d90b4bb7bab11c6a99027ac72bd7e3e) - Created a new sidebar layout system, can be configured with productFiltersLayout in the graphcommerce.config.js ([@paales](https://github.com/paales))
130
+
131
+ ## 6.2.0-canary.49
132
+
133
+ ## 6.2.0-canary.48
134
+
135
+ ## 6.2.0-canary.47
136
+
137
+ ## 6.2.0-canary.46
138
+
139
+ ## 6.2.0-canary.45
140
+
141
+ ## 6.2.0-canary.44
142
+
143
+ ## 6.2.0-canary.43
144
+
145
+ ## 6.2.0-canary.42
146
+
147
+ ## 6.2.0-canary.41
148
+
149
+ ### Patch Changes
150
+
151
+ - [#1960](https://github.com/graphcommerce-org/graphcommerce/pull/1960) [`f78caf5a8`](https://github.com/graphcommerce-org/graphcommerce/commit/f78caf5a83683f1ae4b901fb94bd22d50943fa2f) - Updated packages @apollo/client, react-hook-form, @emotion/\*, @lingui/\*, @mui/\* and various others. ([@paales](https://github.com/paales))
152
+
153
+ ## 6.2.0-canary.40
154
+
155
+ ## 6.2.0-canary.39
156
+
157
+ ## 6.2.0-canary.38
158
+
159
+ ## 6.2.0-canary.37
160
+
161
+ ## 6.2.0-canary.36
162
+
163
+ ## 6.2.0-canary.35
164
+
165
+ ## 6.2.0-canary.34
166
+
167
+ ## 6.2.0-canary.33
168
+
169
+ ## 6.2.0-canary.32
170
+
171
+ ## 6.2.0-canary.31
172
+
173
+ ## 6.2.0-canary.30
174
+
175
+ ## 6.2.0-canary.29
176
+
177
+ ## 6.2.0-canary.28
178
+
179
+ ## 6.2.0-canary.27
180
+
181
+ ## 6.2.0-canary.26
182
+
183
+ ## 6.2.0-canary.25
184
+
185
+ ## 6.2.0-canary.24
186
+
187
+ ## 6.2.0-canary.23
188
+
189
+ ## 6.2.0-canary.22
190
+
191
+ ## 6.2.0-canary.21
4
192
 
5
193
  ## 6.2.0-canary.20
6
194
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/react-hook-form",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "7.0.0-canary.21",
5
+ "version": "7.0.1-canary.0",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,17 +12,17 @@
12
12
  }
13
13
  },
14
14
  "dependencies": {
15
- "@apollo/client": "^3.7.10",
16
- "graphql": "16.6.0",
17
- "react-hook-form": "7.43.5"
15
+ "@apollo/client": "~3.8.2",
16
+ "react-hook-form": "7.46.1"
18
17
  },
19
18
  "devDependencies": {
20
- "@graphcommerce/eslint-config-pwa": "7.0.0-canary.21",
21
- "@graphcommerce/prettier-config-pwa": "7.0.0-canary.21",
22
- "@graphcommerce/typescript-config-pwa": "7.0.0-canary.21",
19
+ "@graphcommerce/eslint-config-pwa": "7.0.1-canary.0",
20
+ "@graphcommerce/prettier-config-pwa": "7.0.1-canary.0",
21
+ "@graphcommerce/typescript-config-pwa": "7.0.1-canary.0",
23
22
  "@testing-library/react": "^14.0.0"
24
23
  },
25
24
  "peerDependencies": {
25
+ "graphql": "^16.6.0",
26
26
  "react": "^18.2.0",
27
27
  "react-dom": "^18.2.0"
28
28
  }
@@ -1,7 +1,20 @@
1
+ import { cloneDeep } from '@apollo/client/utilities'
2
+ // eslint-disable-next-line import/no-extraneous-dependencies
3
+ import { useMemoObject } from '@graphcommerce/next-ui/hooks/useMemoObject'
1
4
  // eslint-disable-next-line import/no-extraneous-dependencies
2
5
  import { debounce } from '@mui/material'
3
- import { useCallback, useEffect, useState } from 'react'
4
- import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form'
6
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
7
+ import {
8
+ Control,
9
+ DeepPartialSkipArrayKey,
10
+ FieldPath,
11
+ FieldValues,
12
+ UseFormReturn,
13
+ useFormState,
14
+ useWatch,
15
+ } from 'react-hook-form'
16
+ import { DebounceOptions } from './utils/debounce'
17
+ import { useDebouncedCallback } from './utils/useDebounceCallback'
5
18
 
6
19
  export type UseFormAutoSubmitOptions<TForm extends UseFormReturn<V>, V extends FieldValues> = {
7
20
  /** Instance of current form */
@@ -18,6 +31,8 @@ export type UseFormAutoSubmitOptions<TForm extends UseFormReturn<V>, V extends F
18
31
  * that this may cause extra requests
19
32
  */
20
33
  forceInitialSubmit?: boolean
34
+ /** Disables the hook */
35
+ disabled?: boolean
21
36
  }
22
37
 
23
38
  /**
@@ -43,7 +58,7 @@ export function useFormAutoSubmit<
43
58
  Form extends UseFormReturn<V>,
44
59
  V extends FieldValues = FieldValues,
45
60
  >(options: UseFormAutoSubmitOptions<Form, V>) {
46
- const { form, submit, wait = 500, fields, forceInitialSubmit } = options
61
+ const { form, submit, wait = 500, fields, forceInitialSubmit, disabled } = options
47
62
  const { formState } = form
48
63
 
49
64
  const [submitting, setSubmitting] = useState(false)
@@ -72,13 +87,88 @@ export function useFormAutoSubmit<
72
87
  )
73
88
 
74
89
  useEffect(() => {
75
- if (canSubmit && (force || shouldSubmit)) {
90
+ if (!disabled && canSubmit && (force || shouldSubmit)) {
76
91
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
77
92
  submitDebounced()
78
93
  return () => submitDebounced.clear()
79
94
  }
80
95
  return () => {}
81
- }, [canSubmit, force, shouldSubmit, submitDebounced])
96
+ }, [canSubmit, force, shouldSubmit, submitDebounced, disabled])
82
97
 
83
98
  return submitting
84
99
  }
100
+
101
+ export type FormAutoSubmitProps<
102
+ TFieldValues extends FieldValues = FieldValues,
103
+ TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
104
+ > = {
105
+ // eslint-disable-next-line react/no-unused-prop-types
106
+ control: Control<TFieldValues>
107
+ /** Autosubmit only when these field names update */
108
+ // eslint-disable-next-line react/no-unused-prop-types
109
+ name?: readonly [...TFieldNames]
110
+
111
+ // eslint-disable-next-line react/no-unused-prop-types
112
+ disabled?: boolean
113
+
114
+ // eslint-disable-next-line react/no-unused-prop-types
115
+ exact?: boolean
116
+
117
+ /** SubmitHandler */
118
+ // eslint-disable-next-line react/no-unused-prop-types
119
+ submit: ReturnType<UseFormReturn<TFieldValues>['handleSubmit']>
120
+
121
+ /**
122
+ * When a current submission is already in flight, should we wait for it to finish before
123
+ * submitting again?
124
+ */
125
+ // eslint-disable-next-line react/no-unused-prop-types
126
+ parallel?: boolean
127
+ } & DebounceOptions
128
+
129
+ function useFormAutoSubmit2<
130
+ TFieldValues extends FieldValues = FieldValues,
131
+ TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
132
+ >(props: FormAutoSubmitProps<TFieldValues, TFieldNames>) {
133
+ const { wait, initialWait, maxWait, submit, parallel, ...watchOptions } = props
134
+
135
+ // We create a stable object from the values, so that we can compare them later
136
+ const values = useMemoObject(cloneDeep(useWatch(watchOptions)))
137
+ const oldValues = useRef<DeepPartialSkipArrayKey<TFieldValues>>(values)
138
+ const { isValidating, isSubmitting, isValid } = useFormState(watchOptions)
139
+
140
+ const submitDebounced = useDebouncedCallback(
141
+ async () => {
142
+ try {
143
+ oldValues.current = values
144
+ await submit()
145
+ } catch (e) {
146
+ // We're not interested if the submission actually succeeds, that should be handled by the form itself.
147
+ }
148
+ },
149
+ { wait, initialWait, maxWait },
150
+ )
151
+
152
+ const valid = isValid && !isValidating
153
+ const allowed = parallel || !isSubmitting
154
+ const canSubmit = valid && allowed
155
+
156
+ if (canSubmit && values !== oldValues.current) {
157
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
158
+ submitDebounced()
159
+ }
160
+ }
161
+
162
+ /**
163
+ * We're wrapping this in a component so that the parent component doesn't rerender on every
164
+ * submission.
165
+ */
166
+ function FormAutoSubmitBase<
167
+ TFieldValues extends FieldValues = FieldValues,
168
+ TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
169
+ >(props: FormAutoSubmitProps<TFieldValues, TFieldNames>) {
170
+ useFormAutoSubmit2(props)
171
+ return null
172
+ }
173
+
174
+ export const FormAutoSubmit = React.memo(FormAutoSubmitBase) as typeof FormAutoSubmitBase
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from 'react'
2
- import { FieldValues, UseFormReturn, Path, FieldPathValue, FieldPath } from 'react-hook-form'
2
+ import { FieldValues, UseFormReturn, Path, FieldPath, PathValue } from 'react-hook-form'
3
3
 
4
4
  export type UseFormPersistOptions<
5
5
  TFieldValues extends FieldValues = FieldValues,
@@ -58,7 +58,7 @@ export function useFormPersist<V extends FieldValues>(options: UseFormPersistOpt
58
58
 
59
59
  const storedValues = JSON.parse(storedFormStr) as FieldValues
60
60
  if (storedValues) {
61
- const entries = Object.entries(storedValues) as [Path<V>, FieldPathValue<V, Path<V>>][]
61
+ const entries = Object.entries(storedValues) as [Path<V>, PathValue<V, Path<V>>][]
62
62
  entries.forEach(([entryName, value]) =>
63
63
  setValue(entryName, value, {
64
64
  shouldDirty: true,
@@ -0,0 +1,69 @@
1
+ export interface Cancelable {
2
+ clear(): void
3
+ }
4
+
5
+ export type DebounceOptions = {
6
+ wait?: number
7
+ maxWait?: number
8
+ initialWait?: number
9
+ }
10
+
11
+ type DebounceState = {
12
+ timeout: ReturnType<typeof setTimeout> | null
13
+ firstCallTime: number | null
14
+ isFirstCall: boolean
15
+ }
16
+
17
+ export default function debounce<T extends (...args: unknown[]) => unknown>({
18
+ func,
19
+ wait = 166,
20
+ maxWait = 100000,
21
+ initialWait = wait,
22
+ }: DebounceOptions & { func: T }): T & Cancelable {
23
+ let state: DebounceState = { timeout: null, firstCallTime: null, isFirstCall: true }
24
+
25
+ // Guidance for developers - Logging warnings for invalid parameter combinations
26
+ if (process.env.NODE_ENV !== 'production') {
27
+ // Rewrite to array
28
+ const params = { wait, initialWait, maxWait }
29
+ const invalidParams = Object.entries(params)
30
+ .filter(([, value]) => value < 0)
31
+ .map(([key]) => key)
32
+
33
+ if (invalidParams.length > 0)
34
+ console.warn(`debounce: ${invalidParams.join(', ')} should not be negative.`)
35
+
36
+ if (maxWait < wait)
37
+ console.warn(`debounce: maxWait should not be less than wait. This does nothing`)
38
+ }
39
+
40
+ const clear = () => {
41
+ if (state.timeout !== null) clearTimeout(state.timeout)
42
+ state = { timeout: null, firstCallTime: null, isFirstCall: true }
43
+ }
44
+
45
+ function debounced<This>(this: This, ...args: Parameters<T>) {
46
+ const now = Date.now()
47
+ state.firstCallTime ??= now
48
+
49
+ const exec = () => {
50
+ state.isFirstCall = false
51
+ clear()
52
+ func.apply(this, args)
53
+ }
54
+
55
+ const delay = state.isFirstCall ? initialWait : wait
56
+
57
+ if (now - state.firstCallTime >= maxWait) {
58
+ exec()
59
+ return
60
+ }
61
+
62
+ if (state.timeout !== null) clearTimeout(state.timeout)
63
+ state.timeout = setTimeout(exec, delay)
64
+ }
65
+
66
+ debounced.clear = clear
67
+
68
+ return debounced as T & Cancelable
69
+ }
@@ -0,0 +1,21 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import useEventCallback from '@mui/utils/useEventCallback'
3
+ import { useEffect, useRef } from 'react'
4
+ import debounce, { DebounceOptions } from './debounce'
5
+
6
+ export function useDebouncedCallback<T extends (...args: unknown[]) => unknown>(
7
+ callback: T,
8
+ { initialWait, maxWait, wait }: DebounceOptions = {},
9
+ ): T {
10
+ const func = useEventCallback(callback)
11
+
12
+ const debounced = useRef(debounce({ func, initialWait, maxWait, wait }))
13
+
14
+ // Re-create the debounced function if the dependencies change
15
+ useEffect(() => {
16
+ debounced.current = debounce({ func, initialWait, maxWait, wait })
17
+ return () => debounced.current.clear()
18
+ }, [func, initialWait, maxWait, wait])
19
+
20
+ return debounced.current
21
+ }