1
- import React , { Component } from "react" ;
1
+ import React , { forwardRef , useImperativeHandle , useRef , useState } from "react" ;
2
2
import PropTypes from "prop-types" ;
3
- import { Form } from "reacto-form" ;
3
+ import { useReactoForm } from "reacto-form" ;
4
4
import { uniqueId } from "lodash" ;
5
5
import { withComponents } from "@reactioncommerce/components-context" ;
6
6
import { CustomPropTypes } from "../../../utils" ;
@@ -11,7 +11,7 @@ import { CustomPropTypes } from "../../../utils";
11
11
* @param {Object } Form object
12
12
* @returns {Object } Transformed object
13
13
*/
14
- function buildResult ( { amount, fullName } ) {
14
+ function buildResult ( { amount, fullName = null } ) {
15
15
let floatAmount = amount ? parseFloat ( amount ) : null ;
16
16
if ( isNaN ( floatAmount ) ) floatAmount = null ;
17
17
@@ -22,128 +22,145 @@ function buildResult({ amount, fullName }) {
22
22
} ;
23
23
}
24
24
25
- class ExampleIOUPaymentForm extends Component {
26
- static propTypes = {
27
- /**
28
- * You can provide a `className` prop that will be applied to the outermost DOM element
29
- * rendered by this component. We do not recommend using this for styling purposes, but
30
- * it can be useful as a selector in some situations.
31
- */
32
- className : PropTypes . string ,
33
- /**
34
- * If you've set up a components context using
35
- * [@reactioncommerce/components-context](https://github.com/reactioncommerce/components-context)
36
- * (recommended), then this prop will come from there automatically. If you have not
37
- * set up a components context or you want to override one of the components in a
38
- * single spot, you can pass in the components prop directly.
39
- */
40
- components : PropTypes . shape ( {
41
- /**
42
- * Pass either the Reaction ErrorsBlock component or your own component that
43
- * accepts compatible props.
44
- */
45
- ErrorsBlock : CustomPropTypes . component . isRequired ,
46
- /**
47
- * Pass either the Reaction Field component or your own component that
48
- * accepts compatible props.
49
- */
50
- Field : CustomPropTypes . component . isRequired ,
51
- /**
52
- * Pass either the Reaction TextInput component or your own component that
53
- * accepts compatible props.
54
- */
55
- TextInput : CustomPropTypes . component . isRequired
56
- } ) ,
57
- /**
58
- * Pass true while the input data is in the process of being saved.
59
- * While true, the form fields are disabled.
60
- */
61
- isSaving : PropTypes . bool ,
62
- /**
63
- * Called as the form fields are changed
64
- */
65
- onChange : PropTypes . func ,
66
- /**
67
- * When this action's input data switches between being
68
- * ready for saving and not ready for saving, this will
69
- * be called with `true` (ready) or `false`
70
- */
71
- onReadyForSaveChange : PropTypes . func ,
72
- /**
73
- * Called with an object value when this component's `submit`
74
- * method is called. The object may have `data`, `displayName`,
75
- * and `amount` properties.
76
- */
77
- onSubmit : PropTypes . func
78
- }
25
+ /**
26
+ * @summary ExampleIOUPaymentForm component
27
+ * @param {Object } props Props
28
+ * @param {Object } ref Ref
29
+ * @return {Object } React render
30
+ */
31
+ function ExampleIOUPaymentForm ( props , ref ) {
32
+ const lastDocRef = useRef ( ) ;
33
+ const isReadyRef = useRef ( ) ;
79
34
80
- static defaultProps = {
81
- onChange ( ) { } ,
82
- onReadyForSaveChange ( ) { } ,
83
- onSubmit ( ) { }
84
- } ;
35
+ const [ uniqueInstanceIdentifier , setUniqueInstanceIdentifier ] = useState ( ) ;
36
+ if ( ! uniqueInstanceIdentifier ) {
37
+ setUniqueInstanceIdentifier ( uniqueId ( "ExampleIOUPaymentForm" ) ) ;
38
+ }
85
39
86
- uniqueInstanceIdentifier = uniqueId ( "ExampleIOUPaymentForm" ) ;
40
+ const {
41
+ className,
42
+ components : {
43
+ ErrorsBlock,
44
+ Field,
45
+ TextInput
46
+ } ,
47
+ isSaving,
48
+ onChange,
49
+ onReadyForSaveChange,
50
+ onSubmit
51
+ } = props ;
87
52
88
- submit ( ) {
89
- if ( this . form ) this . form . submit ( ) ;
90
- }
53
+ const {
54
+ getErrors,
55
+ getInputProps,
56
+ submitForm
57
+ } = useReactoForm ( {
58
+ isReadOnly : isSaving ,
59
+ onChange ( formData ) {
60
+ const resultDoc = buildResult ( formData ) ;
61
+ const stringDoc = JSON . stringify ( resultDoc ) ;
62
+ if ( stringDoc !== lastDocRef . current ) {
63
+ onChange ( resultDoc ) ;
64
+ }
65
+ lastDocRef . current = stringDoc ;
91
66
92
- handleChange = ( doc ) => {
93
- const { onChange, onReadyForSaveChange } = this . props ;
67
+ const isReady = ! ! formData . fullName ;
68
+ if ( isReady !== isReadyRef . current ) {
69
+ onReadyForSaveChange ( isReady ) ;
70
+ }
71
+ isReadyRef . current = isReady ;
72
+ } ,
73
+ onSubmit : ( formData ) => onSubmit ( buildResult ( formData ) )
74
+ } ) ;
94
75
95
- const resultDoc = buildResult ( doc ) ;
96
- const stringDoc = JSON . stringify ( resultDoc ) ;
97
- if ( stringDoc !== this . lastDoc ) {
98
- onChange ( resultDoc ) ;
76
+ useImperativeHandle ( ref , ( ) => ( {
77
+ submit ( ) {
78
+ submitForm ( ) ;
99
79
}
100
- this . lastDoc = stringDoc ;
80
+ } ) ) ;
101
81
102
- const isReady = ! ! doc . fullName ;
103
- if ( isReady !== this . lastIsReady ) {
104
- onReadyForSaveChange ( isReady ) ;
105
- }
106
- this . lastIsReady = isReady ;
107
- }
82
+ const fullNameInputId = `fullName_${ uniqueInstanceIdentifier } ` ;
83
+ const amountInputId = `amount_${ uniqueInstanceIdentifier } ` ;
108
84
109
- handleSubmit = ( doc ) => {
110
- const { onSubmit } = this . props ;
111
- return onSubmit ( buildResult ( doc ) ) ;
112
- }
85
+ return (
86
+ < div className = { className } >
87
+ < Field name = "fullName" errors = { getErrors ( [ "fullName" ] ) } label = "Full name" labelFor = { fullNameInputId } >
88
+ < TextInput id = { fullNameInputId } { ...getInputProps ( "fullName" ) } />
89
+ < ErrorsBlock errors = { getErrors ( [ "fullName" ] ) } />
90
+ </ Field >
91
+ < Field name = "amount" errors = { getErrors ( [ "amount" ] ) } label = "Amount (optional)" labelFor = { amountInputId } >
92
+ < TextInput id = { amountInputId } { ...getInputProps ( "amount" ) } />
93
+ < ErrorsBlock errors = { getErrors ( [ "amount" ] ) } />
94
+ </ Field >
95
+ </ div >
96
+ ) ;
97
+ }
113
98
114
- render ( ) {
115
- const {
116
- className,
117
- components : {
118
- ErrorsBlock,
119
- Field,
120
- TextInput
121
- } ,
122
- isSaving
123
- } = this . props ;
99
+ // There is currently some issue with combining hoist-non-react-statics (used by
100
+ // withComponents) with forwardRef. Until that's resolved, reassigning
101
+ // ExampleIOUPaymentForm to the wrapped component here, before setting the statics.
102
+ /* eslint-disable-next-line no-func-assign */
103
+ ExampleIOUPaymentForm = withComponents ( forwardRef ( ExampleIOUPaymentForm ) ) ;
124
104
125
- const fullNameInputId = `fullName_${ this . uniqueInstanceIdentifier } ` ;
126
- const amountInputId = `amount_${ this . uniqueInstanceIdentifier } ` ;
105
+ ExampleIOUPaymentForm . propTypes = {
106
+ /**
107
+ * You can provide a `className` prop that will be applied to the outermost DOM element
108
+ * rendered by this component. We do not recommend using this for styling purposes, but
109
+ * it can be useful as a selector in some situations.
110
+ */
111
+ className : PropTypes . string ,
112
+ /**
113
+ * If you've set up a components context using
114
+ * [@reactioncommerce/components-context](https://github.com/reactioncommerce/components-context)
115
+ * (recommended), then this prop will come from there automatically. If you have not
116
+ * set up a components context or you want to override one of the components in a
117
+ * single spot, you can pass in the components prop directly.
118
+ */
119
+ components : PropTypes . shape ( {
120
+ /**
121
+ * Pass either the Reaction ErrorsBlock component or your own component that
122
+ * accepts compatible props.
123
+ */
124
+ ErrorsBlock : CustomPropTypes . component . isRequired ,
125
+ /**
126
+ * Pass either the Reaction Field component or your own component that
127
+ * accepts compatible props.
128
+ */
129
+ Field : CustomPropTypes . component . isRequired ,
130
+ /**
131
+ * Pass either the Reaction TextInput component or your own component that
132
+ * accepts compatible props.
133
+ */
134
+ TextInput : CustomPropTypes . component . isRequired
135
+ } ) ,
136
+ /**
137
+ * Pass true while the input data is in the process of being saved.
138
+ * While true, the form fields are disabled.
139
+ */
140
+ isSaving : PropTypes . bool ,
141
+ /**
142
+ * Called as the form fields are changed
143
+ */
144
+ onChange : PropTypes . func ,
145
+ /**
146
+ * When this action's input data switches between being
147
+ * ready for saving and not ready for saving, this will
148
+ * be called with `true` (ready) or `false`
149
+ */
150
+ onReadyForSaveChange : PropTypes . func ,
151
+ /**
152
+ * Called with an object value when this component's `submit`
153
+ * method is called. The object may have `data`, `displayName`,
154
+ * and `amount` properties.
155
+ */
156
+ onSubmit : PropTypes . func
157
+ } ;
127
158
128
- return (
129
- < Form
130
- className = { className }
131
- isReadOnly = { isSaving }
132
- onChange = { this . handleChange }
133
- onSubmit = { this . handleSubmit }
134
- ref = { ( formRef ) => { this . form = formRef ; } }
135
- >
136
- < Field name = "fullName" label = "Full name" labelFor = { fullNameInputId } >
137
- < TextInput id = { fullNameInputId } name = "fullName" />
138
- < ErrorsBlock names = { [ "fullName" ] } />
139
- </ Field >
140
- < Field name = "amount" label = "Amount (optional)" labelFor = { amountInputId } >
141
- < TextInput id = { amountInputId } name = "amount" />
142
- < ErrorsBlock names = { [ "amount" ] } />
143
- </ Field >
144
- </ Form >
145
- ) ;
146
- }
147
- }
159
+ ExampleIOUPaymentForm . defaultProps = {
160
+ isSaving : false ,
161
+ onChange ( ) { } ,
162
+ onReadyForSaveChange ( ) { } ,
163
+ onSubmit ( ) { }
164
+ } ;
148
165
149
- export default withComponents ( ExampleIOUPaymentForm ) ;
166
+ export default ExampleIOUPaymentForm ;
0 commit comments