@graphcommerce/react-hook-form 3.0.6 → 3.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Change Log
2
2
 
3
+ ## 3.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1399](https://github.com/graphcommerce-org/graphcommerce/pull/1399) [`fb277d8e1`](https://github.com/graphcommerce-org/graphcommerce/commit/fb277d8e1e3612c5e9cf890a30d19cfd1ff70542) Thanks [@paales](https://github.com/paales)! - Now using [@graphql-yoga](https://github.com/dotansimha/graphql-yoga) for GraphQL which has full support for [envelop](https://www.envelop.dev/) plugins.
8
+
9
+ * [#1399](https://github.com/graphcommerce-org/graphcommerce/pull/1399) [`fb277d8e1`](https://github.com/graphcommerce-org/graphcommerce/commit/fb277d8e1e3612c5e9cf890a30d19cfd1ff70542) Thanks [@paales](https://github.com/paales)! - Added a new @graphcommerce/cli package to generate the mesh so it can be generated _inside_ the @graphcommerce/graphql-mesh package to allow for better future extensibility.
10
+
11
+ - [#1399](https://github.com/graphcommerce-org/graphcommerce/pull/1399) [`da0ae7d02`](https://github.com/graphcommerce-org/graphcommerce/commit/da0ae7d0236e4908ba0bf0fa16656be516e841d4) Thanks [@paales](https://github.com/paales)! - Updated dependencies
12
+
13
+ ## 3.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [#1379](https://github.com/graphcommerce-org/graphcommerce/pull/1379) [`104abd14e`](https://github.com/graphcommerce-org/graphcommerce/commit/104abd14e1585ef0d8de77937d25156b8fa1e201) Thanks [@paales](https://github.com/paales)! - useFormPersist no accepts a persist array to persist values even if they aren’t dirty anymore
18
+
19
+ * [#1379](https://github.com/graphcommerce-org/graphcommerce/pull/1379) [`2a125b1f9`](https://github.com/graphcommerce-org/graphcommerce/commit/2a125b1f98bb9272d96c3577f21d6c984caad892) Thanks [@paales](https://github.com/paales)! - ComposedSubmit uses the result of form.trigger() method to check if fomrs are valid before atempting to submit any form in the composition
20
+
21
+ ## 3.0.7
22
+
23
+ ### Patch Changes
24
+
25
+ - [#1378](https://github.com/graphcommerce-org/graphcommerce/pull/1378) [`22ff9df16`](https://github.com/graphcommerce-org/graphcommerce/commit/22ff9df1677742ae8e07d9b7e5b12fbb487580dc) Thanks [@paales](https://github.com/paales)! - upgrade to latest versions of packages
26
+
3
27
  ## 3.0.6
4
28
 
5
29
  ### Patch Changes
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": "3.0.6",
5
+ "version": "3.1.1",
6
6
  "sideEffects": false,
7
7
  "engines": {
8
8
  "node": "14.x"
@@ -15,16 +15,16 @@
15
15
  }
16
16
  },
17
17
  "devDependencies": {
18
- "@graphcommerce/eslint-config-pwa": "^4.1.3",
19
- "@graphcommerce/prettier-config-pwa": "^4.0.5",
18
+ "@graphcommerce/eslint-config-pwa": "^4.1.5",
19
+ "@graphcommerce/prettier-config-pwa": "^4.0.6",
20
20
  "@graphcommerce/typescript-config-pwa": "^4.0.2",
21
- "@playwright/test": "^1.20.1",
22
- "type-fest": "2.12.1"
21
+ "@playwright/test": "^1.21.1",
22
+ "type-fest": "^2.12.2"
23
23
  },
24
24
  "dependencies": {
25
25
  "@apollo/client": "^3.5.10",
26
- "graphql": "^16.3.0",
27
- "react-hook-form": "7.28.1"
26
+ "graphql": "16.3.0",
27
+ "react-hook-form": "7.30.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": "^17.0.2",
@@ -46,35 +46,62 @@ export function ComposedSubmit(props: ComposedSubmitProps) {
46
46
  * If we have forms that are have errors, we don't need to actually submit anything yet. We can
47
47
  * trigger the submission of the invalid forms and highlight the errors in those forms.
48
48
  */
49
- let formsToSubmit = formEntries.filter(
50
- ([, f]) => Object.keys(f.form?.formState.errors ?? {}).length > 0,
51
- )
49
+ dispatch({ type: 'SUBMIT' })
52
50
 
53
- // We have no errors and no invalid forms, this means we can submit everything.
54
- if (!formsToSubmit.length) formsToSubmit = formEntries
51
+ const invalidKeys: string[] = []
52
+ for (const [, { form, key }] of formEntries) {
53
+ // eslint-disable-next-line no-await-in-loop
54
+ const result = await form?.trigger()
55
+ if (result === false) invalidKeys.push(key)
56
+ }
55
57
 
56
- dispatch({ type: 'SUBMIT' })
58
+ let formsToProcess = formEntries
59
+ if (invalidKeys.length === 0) {
60
+ if (process.env.NODE_ENV !== 'production') {
61
+ // eslint-disable-next-line no-console
62
+ console.log(
63
+ '[ComposedForm] All forms are valid, submitting...',
64
+ formsToProcess.map(([, { key }]) => key),
65
+ )
66
+ }
67
+ } else {
68
+ formsToProcess = formEntries.filter(([, { key }]) => invalidKeys.includes(key))
69
+ if (process.env.NODE_ENV !== 'production') {
70
+ // eslint-disable-next-line no-console
71
+ console.log(
72
+ '[ComposedForm] Found invalid forms, triggering error messages by submitting...',
73
+ Object.fromEntries(
74
+ formsToProcess.map(([, { key, form }]) => [
75
+ key,
76
+ `Invalid fields ${(form?.formState.errors
77
+ ? Object.keys(form.formState.errors)
78
+ : []
79
+ ).join(', ')}`,
80
+ ]),
81
+ ),
82
+ )
83
+ }
84
+ }
57
85
 
58
86
  try {
59
87
  /**
60
88
  * We're executing these steps all in sequence, since certain forms can depend on other forms
61
89
  * in the backend.
62
- *
63
- * Todo: There might be a performance optimization by submitting multiple forms in parallel.
64
90
  */
65
- let canSubmit = true
66
- for (const [, { submit, form }] of formsToSubmit) {
67
- // eslint-disable-next-line no-await-in-loop
68
- if (canSubmit) await submit?.()
69
- // eslint-disable-next-line no-await-in-loop
70
- if (!canSubmit) await form?.trigger()
71
- if (form && isFormGqlOperation(form) && form.error) {
72
- // console.log(
73
- // key,
74
- // form?.formState.isValid,
75
- // form && (isFormGqlOperation(form) ? form.error : undefined),
76
- // )
77
- canSubmit = false
91
+ for (const [, { submit, key }] of formsToProcess) {
92
+ try {
93
+ // eslint-disable-next-line no-console
94
+ console.log(`[ComposedForm] Submitting ${key}`)
95
+ // eslint-disable-next-line no-await-in-loop
96
+ await submit?.()
97
+ } catch (e) {
98
+ if (process.env.NODE_ENV !== 'production') {
99
+ console.error(
100
+ `[ComposedForm] The form ${key} has thrown an Error during submission, halting submissions`,
101
+ e,
102
+ )
103
+ }
104
+ throw e
78
105
  }
79
106
  }
80
107
  dispatch({ type: 'SUBMITTING' })
@@ -1,6 +1,6 @@
1
1
  import { ApolloError } from '@apollo/client'
2
- import { FieldValues, FormState, UseFormReturn } from 'react-hook-form'
3
- import { SetOptional } from 'type-fest'
2
+ import type { FieldValues, FormState, UseFormReturn } from 'react-hook-form'
3
+ import type { SetOptional } from 'type-fest'
4
4
 
5
5
  export type UseFormComposeOptions<V extends FieldValues = FieldValues> = {
6
6
  /** The form that is used to submit */
@@ -5,22 +5,30 @@ import {
5
5
  Path,
6
6
  FieldPathValue,
7
7
  UnpackNestedValue,
8
+ FieldPath,
8
9
  } from 'react-hook-form'
9
10
 
10
- export type UseFormPersistOptions<V> = {
11
- /** Instance of current form */
12
- form: UseFormReturn<V>
11
+ export type UseFormPersistOptions<
12
+ TFieldValues extends FieldValues = FieldValues,
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ TContext = any,
15
+ > = {
16
+ /** Instance of current form, used to watch value */
17
+ form: UseFormReturn<TFieldValues, TContext>
13
18
 
14
19
  /** Name of the key how it will be stored in the storage. */
15
20
  name: string
16
21
 
17
22
  /**
18
- * SessionStorage: Will not be avaiable when the user returns later (recommended). localStorage:
19
- * Will be available when the user returns later.
23
+ * - `sessionStorage`: Will not be avaiable when the user returns later (recommended).
24
+ * - `localStorage`: Will be available when the user returns later.
20
25
  */
21
26
  storage?: 'sessionStorage' | 'localStorage'
22
27
 
23
- exclude?: string[]
28
+ /** Exclude sensitive data from the storage like passwords. */
29
+ exclude?: FieldPath<TFieldValues>[]
30
+
31
+ persist?: FieldPath<TFieldValues>[]
24
32
  }
25
33
 
26
34
  /**
@@ -28,16 +36,24 @@ export type UseFormPersistOptions<V> = {
28
36
  * dirty fields when the form is initialized
29
37
  */
30
38
  export function useFormPersist<V>(options: UseFormPersistOptions<V>) {
31
- const { form, name, storage = 'sessionStorage', exclude = [] } = options
39
+ const { form, name, storage = 'sessionStorage', exclude = [], persist = [] } = options
32
40
  const { setValue, watch, formState } = form
33
41
 
34
42
  const dirtyFieldKeys = Object.keys(formState.dirtyFields) as Path<V>[]
35
- const valuesJson = JSON.stringify(
36
- Object.fromEntries(
37
- dirtyFieldKeys.filter((f) => !exclude.includes(f)).map((field) => [field, watch(field)]),
38
- ),
43
+
44
+ // Get all dirty field values and exclude sensitive data
45
+ const newValues = Object.fromEntries(
46
+ dirtyFieldKeys.filter((f) => !exclude.includes(f)).map((field) => [field, watch(field)]),
39
47
  )
40
48
 
49
+ // Amend the values with the values that should always be persisted
50
+ persist.forEach((persistKey) => {
51
+ const value = watch(persistKey)
52
+ if (value) newValues[persistKey] = value
53
+ })
54
+
55
+ const valuesJson = JSON.stringify(newValues)
56
+
41
57
  // Restore changes
42
58
  useEffect(() => {
43
59
  try {
@@ -51,7 +67,12 @@ export function useFormPersist<V>(options: UseFormPersistOptions<V>) {
51
67
  Path<V>,
52
68
  UnpackNestedValue<FieldPathValue<V, Path<V>>>,
53
69
  ][]
54
- entries.forEach((entry) => setValue(...entry, { shouldDirty: true, shouldValidate: true }))
70
+ entries.forEach(([entryName, value]) =>
71
+ setValue(entryName, value, {
72
+ shouldDirty: true,
73
+ shouldValidate: true,
74
+ }),
75
+ )
55
76
  }
56
77
  } catch {
57
78
  //
@@ -1,4 +1,4 @@
1
- import { TypedDocumentNode } from '@apollo/client'
1
+ import type { TypedDocumentNode } from '@apollo/client'
2
2
  import {
3
3
  DefinitionNode,
4
4
  OperationDefinitionNode,
@@ -12,8 +12,8 @@ import {
12
12
  OperationTypeNode,
13
13
  } from 'graphql'
14
14
  import { useMemo } from 'react'
15
- import { FieldValues } from 'react-hook-form'
16
- import { LiteralUnion } from 'type-fest'
15
+ import type { FieldValues } from 'react-hook-form'
16
+ import type { LiteralUnion } from 'type-fest'
17
17
 
18
18
  type Scalars = {
19
19
  ID: string
@@ -7,7 +7,7 @@ import {
7
7
  useLazyQuery,
8
8
  } from '@apollo/client'
9
9
  import { useEffect, useRef } from 'react'
10
- import { Promisable } from 'type-fest'
10
+ import type { Promisable } from 'type-fest'
11
11
 
12
12
  export type LazyQueryTuple<Q, V> = [
13
13
  (options?: QueryLazyOptions<V>) => Promisable<LazyQueryResult<Q, V>>,