Skip to content

Commit 0f2fdf3

Browse files
authored
refactor(profile): convert AboutSettings from class to functional component (freeCodeCamp#58568)
1 parent 2eb11a7 commit 0f2fdf3

File tree

1 file changed

+148
-188
lines changed
  • client/src/components/profile/components

1 file changed

+148
-188
lines changed

client/src/components/profile/components/about.tsx

Lines changed: 148 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Component } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import {
33
FormGroup,
44
FormControl,
@@ -32,13 +32,6 @@ type AboutProps = Omit<
3232

3333
type FormValues = Pick<AboutProps, 'name' | 'location' | 'picture' | 'about'>;
3434

35-
type AboutState = {
36-
formValues: FormValues;
37-
originalValues: FormValues;
38-
formClicked: boolean;
39-
isPictureUrlValid: boolean;
40-
};
41-
4235
const ShowImageValidationWarning = ({
4336
alertContent
4437
}: {
@@ -51,227 +44,194 @@ const ShowImageValidationWarning = ({
5144
);
5245
};
5346

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+
});
7779
};
7880

79-
componentDidUpdate() {
80-
const { name, location, picture, about } = this.props;
81-
const { formValues, formClicked } = this.state;
81+
useEffect(() => {
8282
if (
8383
formClicked &&
8484
name === formValues.name &&
8585
location === formValues.location &&
8686
picture === formValues.picture &&
8787
about === formValues.about
8888
) {
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
9794
});
95+
setFormClicked(false);
9896
}
99-
return null;
100-
}
97+
}, [formClicked, name, location, picture, about, formValues]);
10198

102-
isFormPristine = () => {
103-
const { formValues, originalValues } = this.state;
99+
const isFormPristine = () => {
104100
return (
105-
this.state.isPictureUrlValid === false ||
101+
isPictureUrlValid === false ||
106102
(Object.keys(originalValues) as Array<keyof FormValues>)
107103
.map(key => originalValues[key] === formValues[key])
108104
.every(bool => bool)
109105
);
110106
};
111107

112-
handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
108+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
113109
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);
121114
} else {
122-
this.toggleEditing();
123-
return false;
115+
setIsEditing(false);
124116
}
125117
};
126118

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
134124
}));
135125
};
136126

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
144132
}));
145133
};
146134

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);
165137
if (!value) {
166-
this.setState({
167-
isPictureUrlValid: true
168-
});
138+
setIsPictureUrlValid(true);
169139
} 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);
174142
});
143+
} else {
144+
setIsPictureUrlValid(false);
175145
}
176-
this.setState(state => ({
177-
formValues: {
178-
...state.formValues,
179-
picture: value
180-
}
146+
setFormValues(state => ({
147+
...state,
148+
picture: value
181149
}));
182150
};
183151

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
191157
}));
192158
};
193159

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')}
256205
/>
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+
};
275235

276236
AboutSettings.displayName = 'AboutSettings';
277237

0 commit comments

Comments
 (0)