Skip to content

Commit 7652372

Browse files
authored
Merge pull request #3249 from AbhigyanSrivastav/fix/form-reinitialization-on-language-change
Fixes form reinitialization on language change (#3229)
2 parents 5e5ef5f + 57f6e05 commit 7652372

File tree

4 files changed

+232
-185
lines changed

4 files changed

+232
-185
lines changed

Diff for: client/common/useSyncFormTranslations.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useEffect } from 'react';
2+
3+
// Usage: useSyncFormTranslations(formRef, language)
4+
// This hook ensures that form values are preserved when the language changes.
5+
// Pass a ref to the form instance and the current language as arguments.
6+
const useSyncFormTranslations = (formRef, language) => {
7+
useEffect(() => {
8+
const form = formRef.current;
9+
if (!form) return;
10+
11+
const { values } = form.getState();
12+
form.reset();
13+
14+
Object.keys(values).forEach((field) => {
15+
if (values[field]) {
16+
form.change(field, values[field]);
17+
}
18+
});
19+
}, [language]);
20+
};
21+
22+
export default useSyncFormTranslations;

Diff for: client/modules/User/components/LoginForm.jsx

+80-65
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,114 @@
1-
import React, { useState } from 'react';
1+
import React, { useRef, useState } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { Form, Field } from 'react-final-form';
44
import { useDispatch } from 'react-redux';
55
import { AiOutlineEye, AiOutlineEyeInvisible } from 'react-icons/ai';
66
import Button from '../../../common/Button';
77
import { validateLogin } from '../../../utils/reduxFormUtils';
88
import { validateAndLoginUser } from '../actions';
9+
import { useSyncFormTranslations } from '../../../common/useSyncFormTranslations';
910

1011
function LoginForm() {
11-
const { t } = useTranslation();
12+
const { t, i18n } = useTranslation();
1213

1314
const dispatch = useDispatch();
1415
function onSubmit(formProps) {
1516
return dispatch(validateAndLoginUser(formProps));
1617
}
1718
const [showPassword, setShowPassword] = useState(false);
19+
const formRef = useRef(null);
20+
1821
const handleVisibility = () => {
1922
setShowPassword(!showPassword);
2023
};
2124

25+
useSyncFormTranslations(formRef, i18n.language);
26+
2227
return (
2328
<Form
2429
fields={['email', 'password']}
2530
validate={validateLogin}
2631
onSubmit={onSubmit}
2732
>
28-
{({ handleSubmit, submitError, submitting, modifiedSinceLastSubmit }) => (
29-
<form className="form" onSubmit={handleSubmit}>
30-
<Field name="email">
31-
{(field) => (
32-
<div className="form__field">
33-
<label htmlFor="email" className="form__label">
34-
{t('LoginForm.UsernameOrEmail')}
35-
</label>
36-
<input
37-
className="form__input"
38-
aria-label={t('LoginForm.UsernameOrEmailARIA')}
39-
type="text"
40-
id="email"
41-
autoComplete="username"
42-
autoCapitalize="none"
43-
{...field.input}
44-
/>
45-
{field.meta.touched && field.meta.error && (
46-
<span className="form-error" aria-live="polite">
47-
{field.meta.error}
48-
</span>
49-
)}
50-
</div>
51-
)}
52-
</Field>
53-
<Field name="password">
54-
{(field) => (
55-
<div className="form__field">
56-
<label htmlFor="password" className="form__label">
57-
{t('LoginForm.Password')}
58-
</label>
59-
<div className="form__field__password">
33+
{({
34+
handleSubmit,
35+
submitError,
36+
submitting,
37+
modifiedSinceLastSubmit,
38+
form
39+
}) => {
40+
formRef.current = form;
41+
42+
return (
43+
<form className="form" onSubmit={handleSubmit}>
44+
<Field name="email">
45+
{(field) => (
46+
<div className="form__field">
47+
<label htmlFor="email" className="form__label">
48+
{t('LoginForm.UsernameOrEmail')}
49+
</label>
6050
<input
6151
className="form__input"
62-
aria-label={t('LoginForm.PasswordARIA')}
63-
type={showPassword ? 'text' : 'password'}
64-
id="password"
65-
autoComplete="current-password"
52+
aria-label={t('LoginForm.UsernameOrEmailARIA')}
53+
type="text"
54+
id="email"
55+
autoComplete="username"
56+
autoCapitalize="none"
6657
{...field.input}
6758
/>
68-
<button
69-
className="form__eye__icon"
70-
type="button"
71-
onClick={handleVisibility}
72-
aria-hidden="true"
73-
>
74-
{showPassword ? (
75-
<AiOutlineEyeInvisible />
76-
) : (
77-
<AiOutlineEye />
78-
)}
79-
</button>
59+
{field.meta.touched && field.meta.error && (
60+
<span className="form-error" aria-live="polite">
61+
{field.meta.error}
62+
</span>
63+
)}
64+
</div>
65+
)}
66+
</Field>
67+
<Field name="password">
68+
{(field) => (
69+
<div className="form__field">
70+
<label htmlFor="password" className="form__label">
71+
{t('LoginForm.Password')}
72+
</label>
73+
<div className="form__field__password">
74+
<input
75+
className="form__input"
76+
aria-label={t('LoginForm.PasswordARIA')}
77+
type={showPassword ? 'text' : 'password'}
78+
id="password"
79+
autoComplete="current-password"
80+
{...field.input}
81+
/>
82+
<button
83+
className="form__eye__icon"
84+
type="button"
85+
onClick={handleVisibility}
86+
aria-hidden="true"
87+
>
88+
{showPassword ? (
89+
<AiOutlineEyeInvisible />
90+
) : (
91+
<AiOutlineEye />
92+
)}
93+
</button>
94+
</div>
95+
{field.meta.touched && field.meta.error && (
96+
<span className="form-error" aria-live="polite">
97+
{field.meta.error}
98+
</span>
99+
)}
80100
</div>
81-
{field.meta.touched && field.meta.error && (
82-
<span className="form-error" aria-live="polite">
83-
{field.meta.error}
84-
</span>
85-
)}
86-
</div>
101+
)}
102+
</Field>
103+
{submitError && !modifiedSinceLastSubmit && (
104+
<span className="form-error">{submitError}</span>
87105
)}
88-
</Field>
89-
{submitError && !modifiedSinceLastSubmit && (
90-
<span className="form-error">{submitError}</span>
91-
)}
92-
<Button type="submit" disabled={submitting}>
93-
{t('LoginForm.Submit')}
94-
</Button>
95-
</form>
96-
)}
106+
<Button type="submit" disabled={submitting}>
107+
{t('LoginForm.Submit')}
108+
</Button>
109+
</form>
110+
);
111+
}}
97112
</Form>
98113
);
99114
}

Diff for: client/modules/User/components/LoginForm.unit.test.jsx

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ jest.mock('../actions', () => ({
2323
)
2424
}));
2525

26+
jest.mock('../../../common/useSyncFormTranslations', () => ({
27+
useSyncFormTranslations: jest.fn()
28+
}));
29+
2630
const subject = () => {
2731
reduxRender(<LoginForm />, {
2832
store

0 commit comments

Comments
 (0)