Skip to content

fix: render success message via React state instead of DOM mutation (#1161)#1236

Open
wendyamoni-creator wants to merge 1 commit into
solutions-plug:mainfrom
wendyamoni-creator:fix/success-message-live-region-announcement-is-set
Open

fix: render success message via React state instead of DOM mutation (#1161)#1236
wendyamoni-creator wants to merge 1 commit into
solutions-plug:mainfrom
wendyamoni-creator:fix/success-message-live-region-announcement-is-set

Conversation

@wendyamoni-creator

@wendyamoni-creator wendyamoni-creator commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Fixes #1161 — the newsletter signup success message was announced to screen readers via a direct DOM mutation (formStatusRef.current.textContent = ...) rather than through React state, making it a fragile unmanaged side-channel.

Root Cause

After a successful subscription, the code did:

if (formStatusRef.current) {
  formStatusRef.current.textContent = t('hero.successMessage');
}

This bypassed React's render cycle. Any future re-render that gives the role=status div real JSX children, or a state change that re-renders the form, could silently clobber or fail to update the announcement.

Fix

frontend/src/components/LandingPage.tsx

  • Removed formStatusRef (the ref is no longer needed)
  • Removed the textContent mutation
  • The live-region div now renders the message as a JSX child:
<div
  id="form-status"
  role="status"
  aria-live="polite"
  aria-atomic="true"
  className="visually-hidden"
>
  {isSubmitted && t('hero.successMessage')}
</div>

Screen readers continue to receive the announcement because the aria-live="polite" region gets a text child injected by React on the same render that sets isSubmitted = true.

Tests

Added frontend/src/components/__tests__/LandingPage.live-region.test.tsx (5 tests):

  1. Success message appears in #form-status after successful submission
  2. The text is present as a real child node (rendered by React, not imperatively injected)
  3. Live-region is empty before the form is submitted
  4. ARIA attributes (role=status, aria-live=polite, aria-atomic=true) are present
  5. The ref prop is absent from the div (no imperative mutation possible)

Checklist

The success message in the newsletter signup form was set via direct DOM
mutation (formStatusRef.current.textContent = ...) after a successful
subscription. This worked only by chance—React never re-rendered different
children into that node, so the imperative update persisted. Any future
change that adds real JSX children to the live-region div, or triggers a
re-render after success, could silently clobber the announcement.

Fix:
- Remove formStatusRef and the imperative textContent assignment
- Render {isSubmitted && t('hero.successMessage')} as JSX children inside
  the div with role=status aria-live=polite

Tests:
- Add LandingPage.live-region.test.tsx with 5 tests covering:
  • Success message appears in DOM via React state after submission
  • The text is rendered as real child nodes, not injected imperatively
  • Live-region is empty before form submission
  • ARIA attributes (role=status, aria-live=polite, aria-atomic=true) present
  • No imperative mutation occurs (ref prop removed from the div)

All 50 LandingPage tests pass (4 suites). Build succeeds with zero warnings.

Closes solutions-plug#1161
@drips-wave

drips-wave Bot commented Jul 27, 2026

Copy link
Copy Markdown

@wendyamoni-creator Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Success-message live-region announcement is set via direct DOM mutation instead of React state

1 participant