Skip to content

Commit 60eb702

Browse files
authored
fix: enhance a11y, prevent double firing in error rating (vercel#74563)
Improved a11y (aria-live, aria-label) and simplified loading/error state to prevent double firing when feedback buttons get clicked multiple times while loading.
1 parent 7581bef commit 60eb702

File tree

3 files changed

+21
-15
lines changed

3 files changed

+21
-15
lines changed

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-footer/error-feedback/error-feedback.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,28 @@ import { ThumbsDown } from '../../../../icons/thumbs/thumbs-down'
55
interface ErrorFeedbackProps {
66
errorCode: string
77
}
8-
98
export function ErrorFeedback({ errorCode }: ErrorFeedbackProps) {
109
const [voted, setVoted] = useState<boolean | null>(null)
1110
const hasVoted = voted !== null
1211

1312
const handleFeedback = useCallback(
1413
async (wasHelpful: boolean) => {
14+
// Optimistically set feedback state without loading/error states to keep implementation simple
15+
setVoted(wasHelpful)
1516
try {
1617
const response = await fetch(
17-
`${process.env.__NEXT_ROUTER_BASEPATH || ''}/__nextjs_error_feedback?errorCode=${errorCode}&wasHelpful=${wasHelpful}`
18+
`${process.env.__NEXT_ROUTER_BASEPATH || ''}/__nextjs_error_feedback?${new URLSearchParams(
19+
{
20+
errorCode,
21+
wasHelpful: wasHelpful.toString(),
22+
}
23+
)}`
1824
)
1925

2026
if (!response.ok) {
2127
// Handle non-2xx HTTP responses here if needed
2228
console.error('Failed to record feedback on the server.')
2329
}
24-
25-
setVoted(wasHelpful)
2630
} catch (error) {
2731
console.error('Failed to record feedback:', error)
2832
}
@@ -32,27 +36,29 @@ export function ErrorFeedback({ errorCode }: ErrorFeedbackProps) {
3236

3337
return (
3438
<>
35-
<div className="error-feedback">
39+
<div className="error-feedback" role="region" aria-label="Error feedback">
3640
{hasVoted ? (
37-
<p className="error-feedback-thanks">Thanks for your feedback!</p>
41+
<p className="error-feedback-thanks" role="status" aria-live="polite">
42+
Thanks for your feedback!
43+
</p>
3844
) : (
3945
<>
4046
<p>Was this helpful?</p>
4147
<button
4248
aria-label="Mark as helpful"
4349
onClick={() => handleFeedback(true)}
44-
disabled={hasVoted}
4550
className={`feedback-button ${voted === true ? 'voted' : ''}`}
51+
type="button"
4652
>
47-
<ThumbsUp />
53+
<ThumbsUp aria-hidden="true" />
4854
</button>
4955
<button
5056
aria-label="Mark as not helpful"
5157
onClick={() => handleFeedback(false)}
52-
disabled={hasVoted}
5358
className={`feedback-button ${voted === false ? 'voted' : ''}`}
59+
type="button"
5460
>
55-
<ThumbsDown />
61+
<ThumbsDown aria-hidden="true" />
5662
</button>
5763
</>
5864
)}

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/Errors/error-overlay-layout/error-overlay-layout.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,8 @@ describe('ErrorOverlayLayout Component', () => {
6868
screen.queryByText('Thanks for your feedback!')
6969
).not.toBeInTheDocument()
7070

71-
// Click helpful button
7271
await act(async () => {
73-
fireEvent.click(screen.getByLabelText('Mark as helpful'))
72+
fireEvent.click(screen.getByRole('button', { name: 'Mark as helpful' }))
7473
})
7574

7675
expect(fetch).toHaveBeenCalledWith(
@@ -88,7 +87,9 @@ describe('ErrorOverlayLayout Component', () => {
8887
).not.toBeInTheDocument()
8988

9089
await act(async () => {
91-
fireEvent.click(screen.getByLabelText('Mark as not helpful'))
90+
fireEvent.click(
91+
screen.getByRole('button', { name: 'Mark as not helpful' })
92+
)
9293
})
9394

9495
expect(fetch).toHaveBeenCalledWith(

test/e2e/app-dir/app-prefetch/prefetching.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,7 @@ describe('app dir - prefetching', () => {
348348
beforePageLoad(page: Page) {
349349
page.on('request', async (req: Request) => {
350350
const url = new URL(req.url())
351-
const headers = await req.allHeaders()
352-
if (headers['rsc']) {
351+
if (url.searchParams.has('_rsc')) {
353352
rscRequests.push(url.pathname + url.search)
354353
}
355354
})

0 commit comments

Comments
 (0)