1
- import React , { Component } from 'react' ;
1
+ import React , { useEffect , useState } from 'react' ;
2
2
import {
3
3
FormGroup ,
4
4
FormControl ,
@@ -32,13 +32,6 @@ type AboutProps = Omit<
32
32
33
33
type FormValues = Pick < AboutProps , 'name' | 'location' | 'picture' | 'about' > ;
34
34
35
- type AboutState = {
36
- formValues : FormValues ;
37
- originalValues : FormValues ;
38
- formClicked : boolean ;
39
- isPictureUrlValid : boolean ;
40
- } ;
41
-
42
35
const ShowImageValidationWarning = ( {
43
36
alertContent
44
37
} : {
@@ -51,227 +44,194 @@ const ShowImageValidationWarning = ({
51
44
) ;
52
45
} ;
53
46
54
- class AboutSettings extends Component < AboutProps , AboutState > {
55
- validationImage : HTMLImageElement ;
56
- static displayName : string ;
57
- constructor ( props : AboutProps ) {
58
- super ( props ) ;
59
- this . validationImage = new Image ( ) ;
60
- const { name = '' , location = '' , picture = '' , about = '' } = props ;
61
- const values = {
62
- name,
63
- location,
64
- picture,
65
- about
66
- } ;
67
- this . state = {
68
- formValues : { ...values } ,
69
- originalValues : { ...values } ,
70
- formClicked : false ,
71
- isPictureUrlValid : true
72
- } ;
73
- }
74
-
75
- toggleEditing = ( ) => {
76
- this . props . setIsEditing ( false ) ;
47
+ const AboutSettings = ( {
48
+ t,
49
+ name = '' ,
50
+ location = '' ,
51
+ picture = '' ,
52
+ about = '' ,
53
+ submitNewAbout,
54
+ setIsEditing
55
+ } : AboutProps ) => {
56
+ const [ formValues , setFormValues ] = useState < FormValues > ( {
57
+ name,
58
+ location,
59
+ picture,
60
+ about
61
+ } ) ;
62
+ const [ originalValues , setOriginalValues ] = useState < FormValues > ( {
63
+ name,
64
+ location,
65
+ picture,
66
+ about
67
+ } ) ;
68
+ const [ formClicked , setFormClicked ] = useState ( false ) ;
69
+ const [ isPictureUrlValid , setIsPictureUrlValid ] = useState ( true ) ;
70
+
71
+ const checkIfValidImage = ( url : string ) => {
72
+ const img = new Image ( ) ;
73
+
74
+ return new Promise ( resolve => {
75
+ img . onerror = ( ) => resolve ( false ) ;
76
+ img . onload = ( ) => resolve ( true ) ;
77
+ img . src = url ;
78
+ } ) ;
77
79
} ;
78
80
79
- componentDidUpdate ( ) {
80
- const { name, location, picture, about } = this . props ;
81
- const { formValues, formClicked } = this . state ;
81
+ useEffect ( ( ) => {
82
82
if (
83
83
formClicked &&
84
84
name === formValues . name &&
85
85
location === formValues . location &&
86
86
picture === formValues . picture &&
87
87
about === formValues . about
88
88
) {
89
- return this . setState ( {
90
- originalValues : {
91
- name,
92
- location,
93
- picture,
94
- about
95
- } ,
96
- formClicked : false
89
+ setOriginalValues ( {
90
+ name,
91
+ location,
92
+ picture,
93
+ about
97
94
} ) ;
95
+ setFormClicked ( false ) ;
98
96
}
99
- return null ;
100
- }
97
+ } , [ formClicked , name , location , picture , about , formValues ] ) ;
101
98
102
- isFormPristine = ( ) => {
103
- const { formValues, originalValues } = this . state ;
99
+ const isFormPristine = ( ) => {
104
100
return (
105
- this . state . isPictureUrlValid === false ||
101
+ isPictureUrlValid === false ||
106
102
( Object . keys ( originalValues ) as Array < keyof FormValues > )
107
103
. map ( key => originalValues [ key ] === formValues [ key ] )
108
104
. every ( bool => bool )
109
105
) ;
110
106
} ;
111
107
112
- handleSubmit = ( e : React . FormEvent < HTMLFormElement > ) => {
108
+ const handleSubmit = ( e : React . FormEvent < HTMLFormElement > ) => {
113
109
e . preventDefault ( ) ;
114
- const { formValues } = this . state ;
115
- const { submitNewAbout } = this . props ;
116
- if ( this . state . isPictureUrlValid === true && ! this . isFormPristine ( ) ) {
117
- this . toggleEditing ( ) ;
118
- return this . setState ( { formClicked : true } , ( ) =>
119
- submitNewAbout ( formValues )
120
- ) ;
110
+ if ( isPictureUrlValid === true && ! isFormPristine ( ) ) {
111
+ setIsEditing ( false ) ;
112
+ setFormClicked ( true ) ;
113
+ submitNewAbout ( formValues ) ;
121
114
} else {
122
- this . toggleEditing ( ) ;
123
- return false ;
115
+ setIsEditing ( false ) ;
124
116
}
125
117
} ;
126
118
127
- handleNameChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
128
- const value = ( e . target as HTMLInputElement ) . value . slice ( 0 ) ;
129
- return this . setState ( state => ( {
130
- formValues : {
131
- ...state . formValues ,
132
- name : value
133
- }
119
+ const handleNameChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
120
+ const value = e . target . value . slice ( 0 ) ;
121
+ setFormValues ( state => ( {
122
+ ...state ,
123
+ name : value
134
124
} ) ) ;
135
125
} ;
136
126
137
- handleLocationChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
138
- const value = ( e . target as HTMLInputElement ) . value . slice ( 0 ) ;
139
- return this . setState ( state => ( {
140
- formValues : {
141
- ...state . formValues ,
142
- location : value
143
- }
127
+ const handleLocationChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
128
+ const value = e . target . value . slice ( 0 ) ;
129
+ setFormValues ( state => ( {
130
+ ...state ,
131
+ location : value
144
132
} ) ) ;
145
133
} ;
146
134
147
- componentDidMount ( ) {
148
- this . validationImage . addEventListener ( 'error' , this . errorEvent ) ;
149
- this . validationImage . addEventListener ( 'load' , this . loadEvent ) ;
150
- }
151
-
152
- componentWillUnmount ( ) {
153
- this . validationImage . removeEventListener ( 'load' , this . loadEvent ) ;
154
- this . validationImage . removeEventListener ( 'error' , this . errorEvent ) ;
155
- }
156
-
157
- loadEvent = ( ) => this . setState ( { isPictureUrlValid : true } ) ;
158
- errorEvent = ( ) =>
159
- this . setState ( state => ( {
160
- isPictureUrlValid : state . formValues . picture === ''
161
- } ) ) ;
162
-
163
- handlePictureChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
164
- const value = ( e . target as HTMLInputElement ) . value . slice ( 0 ) ;
135
+ const handlePictureChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
136
+ const value = e . target . value . slice ( 0 ) ;
165
137
if ( ! value ) {
166
- this . setState ( {
167
- isPictureUrlValid : true
168
- } ) ;
138
+ setIsPictureUrlValid ( true ) ;
169
139
} else if ( isURL ( value , { require_protocol : true } ) ) {
170
- this . validationImage . src = encodeURI ( value ) ;
171
- } else {
172
- this . setState ( {
173
- isPictureUrlValid : false
140
+ void checkIfValidImage ( value ) . then ( isValid => {
141
+ setIsPictureUrlValid ( isValid as boolean ) ;
174
142
} ) ;
143
+ } else {
144
+ setIsPictureUrlValid ( false ) ;
175
145
}
176
- this . setState ( state => ( {
177
- formValues : {
178
- ...state . formValues ,
179
- picture : value
180
- }
146
+ setFormValues ( state => ( {
147
+ ...state ,
148
+ picture : value
181
149
} ) ) ;
182
150
} ;
183
151
184
- handleAboutChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
185
- const value = ( e . target as HTMLInputElement ) . value . slice ( 0 ) ;
186
- return this . setState ( state => ( {
187
- formValues : {
188
- ...state . formValues ,
189
- about : value
190
- }
152
+ const handleAboutChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
153
+ const value = e . target . value . slice ( 0 ) ;
154
+ setFormValues ( state => ( {
155
+ ...state ,
156
+ about : value
191
157
} ) ) ;
192
158
} ;
193
159
194
- render ( ) {
195
- const {
196
- formValues : { name, location, picture, about }
197
- } = this . state ;
198
- const { t } = this . props ;
199
- return (
200
- < >
201
- < SectionHeader > { t ( 'settings.headings.personal-info' ) } </ SectionHeader >
202
- < FullWidthRow >
203
- < form
204
- id = 'camper-identity'
205
- onSubmit = { this . handleSubmit }
206
- data-playwright-test-label = 'camper-identity'
207
- >
208
- < div role = 'group' aria-label = { t ( 'settings.headings.personal-info' ) } >
209
- < FormGroup controlId = 'about-name' >
210
- < ControlLabel htmlFor = 'about-name-input' >
211
- < strong > { t ( 'settings.labels.name' ) } </ strong >
212
- </ ControlLabel >
213
- < FormControl
214
- onChange = { this . handleNameChange }
215
- type = 'text'
216
- value = { name }
217
- id = 'about-name-input'
218
- />
219
- </ FormGroup >
220
- < FormGroup controlId = 'about-location' >
221
- < ControlLabel htmlFor = 'about-location-input' >
222
- < strong > { t ( 'settings.labels.location' ) } </ strong >
223
- </ ControlLabel >
224
- < FormControl
225
- onChange = { this . handleLocationChange }
226
- type = 'text'
227
- value = { location }
228
- id = 'about-location-input'
229
- />
230
- </ FormGroup >
231
- < FormGroup controlId = 'about-picture' >
232
- < ControlLabel htmlFor = 'about-picture-input' >
233
- < strong > { t ( 'settings.labels.picture' ) } </ strong >
234
- </ ControlLabel >
235
- < FormControl
236
- onChange = { this . handlePictureChange }
237
- type = 'url'
238
- value = { picture }
239
- id = 'about-picture-input'
240
- />
241
- { ! this . state . isPictureUrlValid && (
242
- < ShowImageValidationWarning
243
- alertContent = { t ( 'validation.url-not-image' ) }
244
- />
245
- ) }
246
- </ FormGroup >
247
- < FormGroup controlId = 'about-about' >
248
- < ControlLabel htmlFor = 'about-about-input' >
249
- < strong > { t ( 'settings.labels.about' ) } </ strong >
250
- </ ControlLabel >
251
- < FormControl
252
- componentClass = 'textarea'
253
- onChange = { this . handleAboutChange }
254
- value = { about }
255
- id = 'about-about-input'
160
+ return (
161
+ < >
162
+ < SectionHeader > { t ( 'settings.headings.personal-info' ) } </ SectionHeader >
163
+ < FullWidthRow >
164
+ < form
165
+ id = 'camper-identity'
166
+ onSubmit = { handleSubmit }
167
+ data-playwright-test-label = 'camper-identity'
168
+ >
169
+ < div role = 'group' aria-label = { t ( 'settings.headings.personal-info' ) } >
170
+ < FormGroup controlId = 'about-name' >
171
+ < ControlLabel htmlFor = 'about-name-input' >
172
+ < strong > { t ( 'settings.labels.name' ) } </ strong >
173
+ </ ControlLabel >
174
+ < FormControl
175
+ onChange = { handleNameChange }
176
+ type = 'text'
177
+ value = { formValues . name }
178
+ id = 'about-name-input'
179
+ />
180
+ </ FormGroup >
181
+ < FormGroup controlId = 'about-location' >
182
+ < ControlLabel htmlFor = 'about-location-input' >
183
+ < strong > { t ( 'settings.labels.location' ) } </ strong >
184
+ </ ControlLabel >
185
+ < FormControl
186
+ onChange = { handleLocationChange }
187
+ type = 'text'
188
+ value = { formValues . location }
189
+ id = 'about-location-input'
190
+ />
191
+ </ FormGroup >
192
+ < FormGroup controlId = 'about-picture' >
193
+ < ControlLabel htmlFor = 'about-picture-input' >
194
+ < strong > { t ( 'settings.labels.picture' ) } </ strong >
195
+ </ ControlLabel >
196
+ < FormControl
197
+ onChange = { handlePictureChange }
198
+ type = 'url'
199
+ value = { formValues . picture }
200
+ id = 'about-picture-input'
201
+ />
202
+ { ! isPictureUrlValid && (
203
+ < ShowImageValidationWarning
204
+ alertContent = { t ( 'validation.url-not-image' ) }
256
205
/>
257
- </ FormGroup >
258
- </ div >
259
- < BlockSaveButton
260
- disabled = { this . isFormPristine ( ) }
261
- bgSize = 'large'
262
- { ...( this . isFormPristine ( ) && { tabIndex : - 1 } ) }
263
- >
264
- { t ( 'buttons.save' ) } { ' ' }
265
- < span className = 'sr-only' >
266
- { t ( 'settings.headings.personal-info' ) }
267
- </ span >
268
- </ BlockSaveButton >
269
- </ form >
270
- </ FullWidthRow >
271
- </ >
272
- ) ;
273
- }
274
- }
206
+ ) }
207
+ </ FormGroup >
208
+ < FormGroup controlId = 'about-about' >
209
+ < ControlLabel htmlFor = 'about-about-input' >
210
+ < strong > { t ( 'settings.labels.about' ) } </ strong >
211
+ </ ControlLabel >
212
+ < FormControl
213
+ componentClass = 'textarea'
214
+ onChange = { handleAboutChange }
215
+ value = { formValues . about }
216
+ id = 'about-about-input'
217
+ />
218
+ </ FormGroup >
219
+ </ div >
220
+ < BlockSaveButton
221
+ disabled = { isFormPristine ( ) }
222
+ bgSize = 'large'
223
+ { ...( isFormPristine ( ) && { tabIndex : - 1 } ) }
224
+ >
225
+ { t ( 'buttons.save' ) } { ' ' }
226
+ < span className = 'sr-only' >
227
+ { t ( 'settings.headings.personal-info' ) }
228
+ </ span >
229
+ </ BlockSaveButton >
230
+ </ form >
231
+ </ FullWidthRow >
232
+ </ >
233
+ ) ;
234
+ } ;
275
235
276
236
AboutSettings . displayName = 'AboutSettings' ;
277
237
0 commit comments