@graphcommerce/react-hook-form 6.2.0-canary.8 → 6.2.0-canary.80

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,5 +1,165 @@
1
1
  # Change Log
2
2
 
3
+ ## 6.2.0-canary.80
4
+
5
+ ## 6.2.0-canary.79
6
+
7
+ ## 6.2.0-canary.78
8
+
9
+ ## 6.2.0-canary.77
10
+
11
+ ## 6.2.0-canary.76
12
+
13
+ ## 6.2.0-canary.75
14
+
15
+ ## 6.2.0-canary.74
16
+
17
+ ## 6.2.0-canary.73
18
+
19
+ ## 6.2.0-canary.72
20
+
21
+ ## 6.2.0-canary.71
22
+
23
+ ## 6.2.0-canary.70
24
+
25
+ ## 6.2.0-canary.69
26
+
27
+ ### Patch Changes
28
+
29
+ - [#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))
30
+
31
+ ## 6.2.0-canary.68
32
+
33
+ ## 6.2.0-canary.67
34
+
35
+ ### Patch Changes
36
+
37
+ - [#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))
38
+
39
+ ## 6.2.0-canary.66
40
+
41
+ ## 6.2.0-canary.65
42
+
43
+ ## 6.2.0-canary.64
44
+
45
+ ## 6.2.0-canary.63
46
+
47
+ ## 6.2.0-canary.62
48
+
49
+ ## 6.2.0-canary.61
50
+
51
+ ## 6.2.0-canary.60
52
+
53
+ ## 6.2.0-canary.59
54
+
55
+ ## 6.2.0-canary.58
56
+
57
+ ## 6.2.0-canary.57
58
+
59
+ ## 6.2.0-canary.56
60
+
61
+ ## 6.2.0-canary.55
62
+
63
+ ## 6.2.0-canary.54
64
+
65
+ ## 6.2.0-canary.53
66
+
67
+ ## 6.2.0-canary.52
68
+
69
+ ## 6.2.0-canary.51
70
+
71
+ ## 6.2.0-canary.50
72
+
73
+ ### Minor Changes
74
+
75
+ - [`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))
76
+
77
+ ## 6.2.0-canary.49
78
+
79
+ ## 6.2.0-canary.48
80
+
81
+ ## 6.2.0-canary.47
82
+
83
+ ## 6.2.0-canary.46
84
+
85
+ ## 6.2.0-canary.45
86
+
87
+ ## 6.2.0-canary.44
88
+
89
+ ## 6.2.0-canary.43
90
+
91
+ ## 6.2.0-canary.42
92
+
93
+ ## 6.2.0-canary.41
94
+
95
+ ### Patch Changes
96
+
97
+ - [#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))
98
+
99
+ ## 6.2.0-canary.40
100
+
101
+ ## 6.2.0-canary.39
102
+
103
+ ## 6.2.0-canary.38
104
+
105
+ ## 6.2.0-canary.37
106
+
107
+ ## 6.2.0-canary.36
108
+
109
+ ## 6.2.0-canary.35
110
+
111
+ ## 6.2.0-canary.34
112
+
113
+ ## 6.2.0-canary.33
114
+
115
+ ## 6.2.0-canary.32
116
+
117
+ ## 6.2.0-canary.31
118
+
119
+ ## 6.2.0-canary.30
120
+
121
+ ## 6.2.0-canary.29
122
+
123
+ ## 6.2.0-canary.28
124
+
125
+ ## 6.2.0-canary.27
126
+
127
+ ## 6.2.0-canary.26
128
+
129
+ ## 6.2.0-canary.25
130
+
131
+ ## 6.2.0-canary.24
132
+
133
+ ## 6.2.0-canary.23
134
+
135
+ ## 6.2.0-canary.22
136
+
137
+ ## 6.2.0-canary.21
138
+
139
+ ## 6.2.0-canary.20
140
+
141
+ ## 6.2.0-canary.19
142
+
143
+ ## 6.2.0-canary.18
144
+
145
+ ## 6.2.0-canary.17
146
+
147
+ ## 6.2.0-canary.16
148
+
149
+ ## 6.2.0-canary.15
150
+
151
+ ## 6.2.0-canary.14
152
+
153
+ ## 6.2.0-canary.13
154
+
155
+ ## 6.2.0-canary.12
156
+
157
+ ## 6.2.0-canary.11
158
+
159
+ ## 6.2.0-canary.10
160
+
161
+ ## 6.2.0-canary.9
162
+
3
163
  ## 6.2.0-canary.8
4
164
 
5
165
  ## 6.2.0-canary.7
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": "6.2.0-canary.8",
5
+ "version": "6.2.0-canary.80",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,18 +12,18 @@
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.7.17",
16
+ "react-hook-form": "7.44.3"
18
17
  },
19
18
  "devDependencies": {
20
- "@graphcommerce/eslint-config-pwa": "6.2.0-canary.8",
21
- "@graphcommerce/prettier-config-pwa": "6.2.0-canary.8",
22
- "@graphcommerce/typescript-config-pwa": "6.2.0-canary.8",
19
+ "@graphcommerce/eslint-config-pwa": "6.2.0-canary.80",
20
+ "@graphcommerce/prettier-config-pwa": "6.2.0-canary.80",
21
+ "@graphcommerce/typescript-config-pwa": "6.2.0-canary.80",
23
22
  "@testing-library/react": "^14.0.0"
24
23
  },
25
24
  "peerDependencies": {
26
25
  "react": "^18.2.0",
27
- "react-dom": "^18.2.0"
26
+ "react-dom": "^18.2.0",
27
+ "graphql": "^16.6.0"
28
28
  }
29
29
  }
@@ -1,7 +1,19 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
1
+ /* eslint-disable react/no-unused-prop-types */
2
+ import { cloneDeep } from '@apollo/client/utilities'
3
+ import { useMemoObject } from '@graphcommerce/next-ui/hooks/useMemoObject'
2
4
  import { debounce } from '@mui/material'
3
- import { useCallback, useEffect, useState } from 'react'
4
- import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form'
5
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
6
+ import {
7
+ Control,
8
+ DeepPartialSkipArrayKey,
9
+ FieldPath,
10
+ FieldValues,
11
+ UseFormReturn,
12
+ useFormState,
13
+ useWatch,
14
+ } from 'react-hook-form'
15
+ import { DebounceOptions } from './utils/debounce'
16
+ import { useDebouncedCallback } from './utils/useDebounceCallback'
5
17
 
6
18
  export type UseFormAutoSubmitOptions<TForm extends UseFormReturn<V>, V extends FieldValues> = {
7
19
  /** Instance of current form */
@@ -18,6 +30,8 @@ export type UseFormAutoSubmitOptions<TForm extends UseFormReturn<V>, V extends F
18
30
  * that this may cause extra requests
19
31
  */
20
32
  forceInitialSubmit?: boolean
33
+ /** Disables the hook */
34
+ disabled?: boolean
21
35
  }
22
36
 
23
37
  /**
@@ -43,7 +57,7 @@ export function useFormAutoSubmit<
43
57
  Form extends UseFormReturn<V>,
44
58
  V extends FieldValues = FieldValues,
45
59
  >(options: UseFormAutoSubmitOptions<Form, V>) {
46
- const { form, submit, wait = 500, fields, forceInitialSubmit } = options
60
+ const { form, submit, wait = 500, fields, forceInitialSubmit, disabled } = options
47
61
  const { formState } = form
48
62
 
49
63
  const [submitting, setSubmitting] = useState(false)
@@ -72,13 +86,82 @@ export function useFormAutoSubmit<
72
86
  )
73
87
 
74
88
  useEffect(() => {
75
- if (canSubmit && (force || shouldSubmit)) {
89
+ if (!disabled && canSubmit && (force || shouldSubmit)) {
76
90
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
77
91
  submitDebounced()
78
92
  return () => submitDebounced.clear()
79
93
  }
80
94
  return () => {}
81
- }, [canSubmit, force, shouldSubmit, submitDebounced])
95
+ }, [canSubmit, force, shouldSubmit, submitDebounced, disabled])
82
96
 
83
97
  return submitting
84
98
  }
99
+
100
+ export type FormAutoSubmitProps<
101
+ TFieldValues extends FieldValues = FieldValues,
102
+ TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
103
+ > = {
104
+ control: Control<TFieldValues>
105
+ /** Autosubmit only when these field names update */
106
+ name?: readonly [...TFieldNames]
107
+
108
+ disabled?: boolean
109
+
110
+ exact?: boolean
111
+
112
+ /** SubmitHandler */
113
+ submit: ReturnType<UseFormReturn<TFieldValues>['handleSubmit']>
114
+
115
+ /**
116
+ * When a current submission is already in flight, should we wait for it to finish before
117
+ * submitting again?
118
+ */
119
+ parallel?: boolean
120
+ } & DebounceOptions
121
+
122
+ function useFormAutoSubmit2<
123
+ TFieldValues extends FieldValues = FieldValues,
124
+ TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
125
+ >(props: FormAutoSubmitProps<TFieldValues, TFieldNames>) {
126
+ const { wait, initialWait, maxWait, submit, parallel, ...watchOptions } = props
127
+
128
+ // We create a stable object from the values, so that we can compare them later
129
+ const values = useMemoObject(cloneDeep(useWatch(watchOptions)))
130
+ const oldValues = useRef<DeepPartialSkipArrayKey<TFieldValues>>(values)
131
+ const { isValidating, isSubmitting, isValid } = useFormState(watchOptions)
132
+
133
+ const submitDebounced = useDebouncedCallback(
134
+ async () => {
135
+ try {
136
+ oldValues.current = values
137
+ await submit()
138
+ } catch (e) {
139
+ // We're not interested if the submission actually succeeds, that should be handled by the form itself.
140
+ }
141
+ },
142
+ { wait, initialWait, maxWait },
143
+ )
144
+
145
+ const valid = isValid && !isValidating
146
+ const allowed = parallel || !isSubmitting
147
+ const canSubmit = valid && allowed
148
+
149
+ if (canSubmit && values !== oldValues.current) {
150
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
151
+ submitDebounced()
152
+ }
153
+ }
154
+
155
+ /**
156
+ * We're wrapping this in a component so that the parent component doesn't rerender on every
157
+ * submission.
158
+ */
159
+ function FormAutoSubmitBase<
160
+ TFieldValues extends FieldValues = FieldValues,
161
+ TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
162
+ >(props: FormAutoSubmitProps<TFieldValues, TFieldNames>) {
163
+ useFormAutoSubmit2(props)
164
+ return null
165
+ }
166
+
167
+ 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,20 @@
1
+ import useEventCallback from '@mui/utils/useEventCallback'
2
+ import { useEffect, useRef } from 'react'
3
+ import debounce, { DebounceOptions } from './debounce'
4
+
5
+ export function useDebouncedCallback<T extends (...args: unknown[]) => unknown>(
6
+ callback: T,
7
+ { initialWait, maxWait, wait }: DebounceOptions = {},
8
+ ): T {
9
+ const func = useEventCallback(callback) as T
10
+
11
+ const debounced = useRef(debounce({ func, initialWait, maxWait, wait }))
12
+
13
+ // Re-create the debounced function if the dependencies change
14
+ useEffect(() => {
15
+ debounced.current = debounce({ func, initialWait, maxWait, wait })
16
+ return () => debounced.current.clear()
17
+ }, [func, initialWait, maxWait, wait])
18
+
19
+ return debounced.current
20
+ }