Skip to content

Commit b97b2d5

Browse files
@W-19143871 Clear email footer validation messages upon keypress.
1 parent 77db307 commit b97b2d5

File tree

2 files changed

+104
-7
lines changed

2 files changed

+104
-7
lines changed

packages/template-retail-react-app/app/components/subscription/hooks/use-email-subscription.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8-
import {useCallback, useMemo, useState} from 'react'
8+
import {useCallback, useEffect, useMemo, useState} from 'react'
99
import {useForm} from 'react-hook-form'
1010
import {
1111
CONSENT_CHANNELS,
@@ -47,9 +47,21 @@ export const useEmailSubscription = ({tag} = {}) => {
4747

4848
const {formatMessage} = useIntl()
4949

50-
const form = useForm({defaultValues: {email: ''}})
50+
const form = useForm({defaultValues: {email: ''}, reValidateMode: 'onSubmit'})
5151
const [successMessage, setSuccessMessage] = useState(null)
5252

53+
// Clear all feedback (success message + validation errors) when the user types.
54+
// Prevents stale messages from a previous submission from lingering.
55+
useEffect(() => {
56+
const {unsubscribe} = form.watch((_, {name}) => {
57+
if (name === 'email') {
58+
setSuccessMessage(null)
59+
form.clearErrors('email')
60+
}
61+
})
62+
return () => unsubscribe()
63+
}, [form])
64+
5365
const messages = useMemo(
5466
() => ({
5567
success: formatMessage({
@@ -113,11 +125,7 @@ export const useEmailSubscription = ({tag} = {}) => {
113125
// Required by react-hook-form's Proxy-based formState.
114126
const {errors, isSubmitting} = form.formState
115127

116-
const onSubmit = isFeatureEnabled
117-
? form.handleSubmit(submitSubscription)
118-
: (e) => {
119-
if (e?.preventDefault) e.preventDefault()
120-
}
128+
const onSubmit = form.handleSubmit(submitSubscription)
121129

122130
return {form, onSubmit, successMessage, errors, isSubmitting}
123131
}

packages/template-retail-react-app/app/components/subscription/hooks/use-email-subscription.test.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,95 @@ describe('useEmailSubscription', () => {
772772
})
773773
})
774774

775+
describe('Feedback lifecycle — clearing on typing', () => {
776+
test('clears success message when user types after successful submit', async () => {
777+
mockUpdateSubscriptions.mockResolvedValue({})
778+
const {result} = renderHook(() => useEmailSubscription({tag: 'email_capture'}), {
779+
wrapper: createWrapper()
780+
})
781+
782+
act(() => {
783+
result.current.form.setValue('email', 'test@example.com')
784+
})
785+
786+
await act(async () => {
787+
await result.current.onSubmit()
788+
})
789+
790+
await waitFor(() => {
791+
expect(result.current.successMessage).toBe('Thanks for subscribing!')
792+
})
793+
794+
// User starts typing a new email — success message should clear
795+
act(() => {
796+
result.current.form.setValue('email', 'n')
797+
})
798+
799+
await waitFor(() => {
800+
expect(result.current.successMessage).toBeNull()
801+
})
802+
})
803+
804+
test('does not show validation error when typing partial email after successful submit', async () => {
805+
mockUpdateSubscriptions.mockResolvedValue({})
806+
const {result} = renderHook(() => useEmailSubscription({tag: 'email_capture'}), {
807+
wrapper: createWrapper()
808+
})
809+
810+
act(() => {
811+
result.current.form.setValue('email', 'test@example.com')
812+
})
813+
814+
await act(async () => {
815+
await result.current.onSubmit()
816+
})
817+
818+
await waitFor(() => {
819+
expect(result.current.successMessage).toBe('Thanks for subscribing!')
820+
})
821+
822+
// Type a partial (invalid) email — should NOT trigger validation error
823+
act(() => {
824+
result.current.form.setValue('email', 'new-use')
825+
})
826+
827+
await waitFor(() => {
828+
expect(result.current.errors.email).toBeUndefined()
829+
expect(result.current.successMessage).toBeNull()
830+
})
831+
})
832+
833+
test('clears error message when user types after failed submit', async () => {
834+
mockUpdateSubscriptions.mockRejectedValue(new Error('API Error'))
835+
const {result} = renderHook(() => useEmailSubscription({tag: 'email_capture'}), {
836+
wrapper: createWrapper()
837+
})
838+
839+
act(() => {
840+
result.current.form.setValue('email', 'test@example.com')
841+
})
842+
843+
await act(async () => {
844+
await result.current.onSubmit()
845+
})
846+
847+
await waitFor(() => {
848+
expect(result.current.errors.email?.message).toBe(
849+
"We couldn't process the subscription. Try again."
850+
)
851+
})
852+
853+
// User starts correcting — error should clear
854+
act(() => {
855+
result.current.form.setValue('email', 'test@example.co')
856+
})
857+
858+
await waitFor(() => {
859+
expect(result.current.errors.email).toBeUndefined()
860+
})
861+
})
862+
})
863+
775864
describe('Edge cases', () => {
776865
test('handles undefined subscriptions data', () => {
777866
const mockRefetch = jest.fn().mockResolvedValue({data: undefined})

0 commit comments

Comments
 (0)