From a7b7d7f0f9895d6a0a73958aff280c1d78fa7ecd Mon Sep 17 00:00:00 2001 From: Abhigyan Date: Wed, 16 Oct 2024 14:56:07 +0530 Subject: [PATCH 1/5] Fixes form reinitialization on language change (#3229) --- client/modules/User/components/LoginForm.jsx | 10 ++++++++-- client/modules/User/components/SignupForm.jsx | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/client/modules/User/components/LoginForm.jsx b/client/modules/User/components/LoginForm.jsx index becf41137..c7d8b75b4 100644 --- a/client/modules/User/components/LoginForm.jsx +++ b/client/modules/User/components/LoginForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Form, Field } from 'react-final-form'; import { useDispatch } from 'react-redux'; @@ -8,22 +8,28 @@ import { validateLogin } from '../../../utils/reduxFormUtils'; import { validateAndLoginUser } from '../actions'; function LoginForm() { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const dispatch = useDispatch(); function onSubmit(formProps) { return dispatch(validateAndLoginUser(formProps)); } const [showPassword, setShowPassword] = useState(false); + const [formUpdateKey, setFormUpdateKey] = useState(false); + const handleVisibility = () => { setShowPassword(!showPassword); }; + useEffect(() => { + setFormUpdateKey(!formUpdateKey); + }, [i18n.language]); return (
{({ handleSubmit, submitError, submitting, modifiedSinceLastSubmit }) => ( diff --git a/client/modules/User/components/SignupForm.jsx b/client/modules/User/components/SignupForm.jsx index 75ab6910e..ac83e36c6 100644 --- a/client/modules/User/components/SignupForm.jsx +++ b/client/modules/User/components/SignupForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Form, Field } from 'react-final-form'; import { useDispatch } from 'react-redux'; @@ -34,7 +34,7 @@ function validateEmail(email) { } function SignupForm() { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const dispatch = useDispatch(); function onSubmit(formProps) { @@ -42,18 +42,23 @@ function SignupForm() { } const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const [formUpdateKey, setFormUpdateKey] = useState(false); const handleVisibility = () => { setShowPassword(!showPassword); }; const handleConfirmVisibility = () => { setShowConfirmPassword(!showConfirmPassword); }; + useEffect(() => { + setFormUpdateKey(!formUpdateKey); + }, [i18n.language]); return ( {({ handleSubmit, pristine, submitting, invalid }) => ( From d8bf87906c1005294ddcd62ac7927c292c1b319b Mon Sep 17 00:00:00 2001 From: Abhigyan Date: Sun, 3 Nov 2024 15:44:48 +0530 Subject: [PATCH 2/5] imporved:user touched values are persisited while translating the errors --- client/modules/User/components/LoginForm.jsx | 155 ++++++----- client/modules/User/components/SignupForm.jsx | 258 +++++++++--------- 2 files changed, 224 insertions(+), 189 deletions(-) diff --git a/client/modules/User/components/LoginForm.jsx b/client/modules/User/components/LoginForm.jsx index c7d8b75b4..f25029d05 100644 --- a/client/modules/User/components/LoginForm.jsx +++ b/client/modules/User/components/LoginForm.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Form, Field } from 'react-final-form'; import { useDispatch } from 'react-redux'; @@ -15,13 +15,25 @@ function LoginForm() { return dispatch(validateAndLoginUser(formProps)); } const [showPassword, setShowPassword] = useState(false); - const [formUpdateKey, setFormUpdateKey] = useState(false); + const formRef = useRef(null); const handleVisibility = () => { setShowPassword(!showPassword); }; useEffect(() => { - setFormUpdateKey(!formUpdateKey); + const form = formRef.current; + if (!form) return; + + const { values } = form.getState(); // store current form touched values + form.reset(); + + // Restore prev form values and trigger validation + Object.keys(values).forEach((field) => { + if (values[field]) { + // Only reapply touched values + form.change(field, values[field]); + } + }); }, [i18n.language]); return ( @@ -29,77 +41,86 @@ function LoginForm() { fields={['email', 'password']} validate={validateLogin} onSubmit={onSubmit} - key={formUpdateKey} > - {({ handleSubmit, submitError, submitting, modifiedSinceLastSubmit }) => ( - - - {(field) => ( -
- - - {field.meta.touched && field.meta.error && ( - - {field.meta.error} - - )} -
- )} -
- - {(field) => ( -
- -
+ {({ + handleSubmit, + submitError, + submitting, + modifiedSinceLastSubmit, + form + }) => { + formRef.current = form; + + return ( + + + {(field) => ( +
+ - + {field.meta.touched && field.meta.error && ( + + {field.meta.error} + + )} +
+ )} +
+ + {(field) => ( +
+ +
+ + +
+ {field.meta.touched && field.meta.error && ( + + {field.meta.error} + + )}
- {field.meta.touched && field.meta.error && ( - - {field.meta.error} - - )} -
+ )} + + {submitError && !modifiedSinceLastSubmit && ( + {submitError} )} - - {submitError && !modifiedSinceLastSubmit && ( - {submitError} - )} - - - )} + + + ); + }} ); } diff --git a/client/modules/User/components/SignupForm.jsx b/client/modules/User/components/SignupForm.jsx index ac83e36c6..2fe2e6061 100644 --- a/client/modules/User/components/SignupForm.jsx +++ b/client/modules/User/components/SignupForm.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Form, Field } from 'react-final-form'; import { useDispatch } from 'react-redux'; @@ -42,7 +42,7 @@ function SignupForm() { } const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const [formUpdateKey, setFormUpdateKey] = useState(false); + const formRef = useRef(null); const handleVisibility = () => { setShowPassword(!showPassword); }; @@ -50,7 +50,19 @@ function SignupForm() { setShowConfirmPassword(!showConfirmPassword); }; useEffect(() => { - setFormUpdateKey(!formUpdateKey); + const form = formRef.current; + if (!form) return; + + const { values } = form.getState(); // store current form touched values + form.reset(); + + // Restore prev form values and trigger validation + Object.keys(values).forEach((field) => { + if (values[field]) { + // Only reapply touched values + form.change(field, values[field]); + } + }); }, [i18n.language]); return ( @@ -58,136 +70,138 @@ function SignupForm() { fields={['username', 'email', 'password', 'confirmPassword']} validate={validateSignup} onSubmit={onSubmit} - key={formUpdateKey} > - {({ handleSubmit, pristine, submitting, invalid }) => ( -
- - {(field) => ( -
- - - {field.meta.touched && field.meta.error && ( - - {field.meta.error} - - )} -
- )} -
- - {(field) => ( -
- - - {field.meta.touched && field.meta.error && ( - - {field.meta.error} - - )} -
- )} -
- - {(field) => ( -
- -
+ {({ handleSubmit, pristine, submitting, invalid, form }) => { + formRef.current = form; + return ( + + + {(field) => ( +
+ - + {field.meta.touched && field.meta.error && ( + + {field.meta.error} + + )}
- {field.meta.touched && field.meta.error && ( - - {field.meta.error} - - )} -
- )} - - - {(field) => ( -
- -
+ )} + + + {(field) => ( +
+ - + {field.meta.touched && field.meta.error && ( + + {field.meta.error} + + )} +
+ )} +
+ + {(field) => ( +
+ +
+ + +
+ {field.meta.touched && field.meta.error && ( + + {field.meta.error} + + )} +
+ )} +
+ + {(field) => ( +
+ +
+ + +
+ {field.meta.touched && field.meta.error && ( + + {field.meta.error} + + )}
- {field.meta.touched && field.meta.error && ( - - {field.meta.error} - - )} -
- )} - - - - )} + )} + + + + ); + }} ); } From be3d961ec00b7a1d8e301b20877f035c8276838a Mon Sep 17 00:00:00 2001 From: Abhigyan Date: Sat, 16 Nov 2024 11:52:24 +0530 Subject: [PATCH 3/5] extracted common code into a custom hook,removed commnets --- client/modules/IDE/hooks/custom-hooks.js | 19 +++++++++++++++++++ client/modules/User/components/LoginForm.jsx | 18 +++--------------- client/modules/User/components/SignupForm.jsx | 19 +++---------------- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/client/modules/IDE/hooks/custom-hooks.js b/client/modules/IDE/hooks/custom-hooks.js index 1b2c14e44..1683b27b7 100644 --- a/client/modules/IDE/hooks/custom-hooks.js +++ b/client/modules/IDE/hooks/custom-hooks.js @@ -68,3 +68,22 @@ export const useEventListener = ( document.addEventListener(event, callback, useCapture); return () => document.removeEventListener(event, callback, useCapture); }, list); + +// Usage: usePreserveFormValuesOnLanguageChange(formRef, language) +// This hook ensures that form values are preserved when the language changes. +// Pass a ref to the form instance and the current language as arguments. +export const usePreserveFormValuesOnLanguageChange = (formRef, language) => { + useEffect(() => { + const form = formRef.current; + if (!form) return; + + const { values } = form.getState(); + form.reset(); + + Object.keys(values).forEach((field) => { + if (values[field]) { + form.change(field, values[field]); + } + }); + }, [language]); +}; diff --git a/client/modules/User/components/LoginForm.jsx b/client/modules/User/components/LoginForm.jsx index f25029d05..aaa700fe2 100644 --- a/client/modules/User/components/LoginForm.jsx +++ b/client/modules/User/components/LoginForm.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Form, Field } from 'react-final-form'; import { useDispatch } from 'react-redux'; @@ -6,6 +6,7 @@ import { AiOutlineEye, AiOutlineEyeInvisible } from 'react-icons/ai'; import Button from '../../../common/Button'; import { validateLogin } from '../../../utils/reduxFormUtils'; import { validateAndLoginUser } from '../actions'; +import { usePreserveFormValuesOnLanguageChange } from '../../IDE/hooks/custom-hooks'; function LoginForm() { const { t, i18n } = useTranslation(); @@ -20,21 +21,8 @@ function LoginForm() { const handleVisibility = () => { setShowPassword(!showPassword); }; - useEffect(() => { - const form = formRef.current; - if (!form) return; - const { values } = form.getState(); // store current form touched values - form.reset(); - - // Restore prev form values and trigger validation - Object.keys(values).forEach((field) => { - if (values[field]) { - // Only reapply touched values - form.change(field, values[field]); - } - }); - }, [i18n.language]); + usePreserveFormValuesOnLanguageChange(formRef, i18n.language); return (
{ setShowConfirmPassword(!showConfirmPassword); }; - useEffect(() => { - const form = formRef.current; - if (!form) return; - - const { values } = form.getState(); // store current form touched values - form.reset(); - - // Restore prev form values and trigger validation - Object.keys(values).forEach((field) => { - if (values[field]) { - // Only reapply touched values - form.change(field, values[field]); - } - }); - }, [i18n.language]); + usePreserveFormValuesOnLanguageChange(formRef, i18n.language); return ( Date: Mon, 18 Nov 2024 10:03:34 -0500 Subject: [PATCH 4/5] update hook for preserving form values on language change --- client/common/useSyncFormTranslations.js | 22 +++++++++++++++++++ client/modules/IDE/hooks/custom-hooks.js | 19 ---------------- client/modules/User/components/LoginForm.jsx | 4 ++-- client/modules/User/components/SignupForm.jsx | 4 ++-- 4 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 client/common/useSyncFormTranslations.js diff --git a/client/common/useSyncFormTranslations.js b/client/common/useSyncFormTranslations.js new file mode 100644 index 000000000..411c94236 --- /dev/null +++ b/client/common/useSyncFormTranslations.js @@ -0,0 +1,22 @@ +import { useEffect } from 'react'; + +// Usage: useSyncFormTranslations(formRef, language) +// This hook ensures that form values are preserved when the language changes. +// Pass a ref to the form instance and the current language as arguments. +const useSyncFormTranslations = (formRef, language) => { + useEffect(() => { + const form = formRef.current; + if (!form) return; + + const { values } = form.getState(); + form.reset(); + + Object.keys(values).forEach((field) => { + if (values[field]) { + form.change(field, values[field]); + } + }); + }, [language]); +}; + +export default useSyncFormTranslations; diff --git a/client/modules/IDE/hooks/custom-hooks.js b/client/modules/IDE/hooks/custom-hooks.js index 1683b27b7..1b2c14e44 100644 --- a/client/modules/IDE/hooks/custom-hooks.js +++ b/client/modules/IDE/hooks/custom-hooks.js @@ -68,22 +68,3 @@ export const useEventListener = ( document.addEventListener(event, callback, useCapture); return () => document.removeEventListener(event, callback, useCapture); }, list); - -// Usage: usePreserveFormValuesOnLanguageChange(formRef, language) -// This hook ensures that form values are preserved when the language changes. -// Pass a ref to the form instance and the current language as arguments. -export const usePreserveFormValuesOnLanguageChange = (formRef, language) => { - useEffect(() => { - const form = formRef.current; - if (!form) return; - - const { values } = form.getState(); - form.reset(); - - Object.keys(values).forEach((field) => { - if (values[field]) { - form.change(field, values[field]); - } - }); - }, [language]); -}; diff --git a/client/modules/User/components/LoginForm.jsx b/client/modules/User/components/LoginForm.jsx index aaa700fe2..eb6eac840 100644 --- a/client/modules/User/components/LoginForm.jsx +++ b/client/modules/User/components/LoginForm.jsx @@ -6,7 +6,7 @@ import { AiOutlineEye, AiOutlineEyeInvisible } from 'react-icons/ai'; import Button from '../../../common/Button'; import { validateLogin } from '../../../utils/reduxFormUtils'; import { validateAndLoginUser } from '../actions'; -import { usePreserveFormValuesOnLanguageChange } from '../../IDE/hooks/custom-hooks'; +import { useSyncFormTranslations } from '../../../common/useSyncFormTranslations'; function LoginForm() { const { t, i18n } = useTranslation(); @@ -22,7 +22,7 @@ function LoginForm() { setShowPassword(!showPassword); }; - usePreserveFormValuesOnLanguageChange(formRef, i18n.language); + useSyncFormTranslations(formRef, i18n.language); return ( { setShowConfirmPassword(!showConfirmPassword); }; - usePreserveFormValuesOnLanguageChange(formRef, i18n.language); + useSyncFormTranslations(formRef, i18n.language); return ( Date: Mon, 18 Nov 2024 10:23:34 -0500 Subject: [PATCH 5/5] add hook to loginform test --- client/modules/User/components/LoginForm.unit.test.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/modules/User/components/LoginForm.unit.test.jsx b/client/modules/User/components/LoginForm.unit.test.jsx index 28fb9383c..2f4262c16 100644 --- a/client/modules/User/components/LoginForm.unit.test.jsx +++ b/client/modules/User/components/LoginForm.unit.test.jsx @@ -23,6 +23,10 @@ jest.mock('../actions', () => ({ ) })); +jest.mock('../../../common/useSyncFormTranslations', () => ({ + useSyncFormTranslations: jest.fn() +})); + const subject = () => { reduxRender(, { store