Skip to content

Commit 74ce6b2

Browse files
authored
Reapply "Stabilize catchError and retry by removing unstable_ prefix" (#94623)
### What? Reverts #94617, which had reverted #94610. This re-lands the stabilization of the `catchError` API and the `retry` error prop by removing their `unstable_` prefix across source, docs, and tests. ### Why? #94610 was reverted in #94617 to unblock another change. This re-applies it now that it is no longer blocked. ### How? `git revert` of the revert commit. The repo's custom errors.json merge driver handled the error registry: the original codes `1324`/`1325` had been reclaimed by `instant` errors after the revert, so the reintroduced messages were minted as new codes `1360` (`retry()` can only be used in the App Router) and `1361` (`catchError` can only be used in Client Components), keeping errors.json append-only. The docs changelog adds `v16.3.0 | catchError became stable.` while retaining the historical `v16.2.0 | unstable_catchError introduced.` row. ### Verification - `pnpm update-error-codes` (check_error_codes passes; errors.json in sync) - `pnpm build` (full JS build, exit 0) - `pnpm --filter=next types` (exit 0) - Not run: Rust/cargo build (`react_server_components.rs` change is a one-line allow-list string revert; left to CI) <!-- NEXT_JS_LLM_PR -->
1 parent 0553b34 commit 74ce6b2

35 files changed

Lines changed: 203 additions & 215 deletions

File tree

crates/next-custom-transforms/src/transforms/react_server_components.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ impl ReactServerComponentValidator {
671671
"useFormState",
672672
],
673673
),
674-
(atom!("next/error").into(), vec!["unstable_catchError"]),
674+
(atom!("next/error").into(), vec!["catchError"]),
675675
(
676676
atom!("next/navigation").into(),
677677
vec![

docs/01-app/01-getting-started/10-error-handling.mdx

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ import { useEffect } from 'react'
215215

216216
export default function ErrorPage({
217217
error,
218-
unstable_retry,
218+
retry,
219219
}: {
220220
error: Error & { digest?: string }
221-
unstable_retry: () => void
221+
retry: () => void
222222
}) {
223223
useEffect(() => {
224224
// Log the error to an error reporting service
@@ -231,7 +231,7 @@ export default function ErrorPage({
231231
<button
232232
onClick={
233233
// Attempt to recover by re-fetching and re-rendering the segment
234-
() => unstable_retry()
234+
() => retry()
235235
}
236236
>
237237
Try again
@@ -246,7 +246,7 @@ export default function ErrorPage({
246246

247247
import { useEffect } from 'react'
248248

249-
export default function ErrorPage({ error, unstable_retry }) {
249+
export default function ErrorPage({ error, retry }) {
250250
useEffect(() => {
251251
// Log the error to an error reporting service
252252
console.error(error)
@@ -258,7 +258,7 @@ export default function ErrorPage({ error, unstable_retry }) {
258258
<button
259259
onClick={
260260
// Attempt to recover by re-fetching and re-rendering the segment
261-
() => unstable_retry()
261+
() => retry()
262262
}
263263
>
264264
Try again
@@ -278,22 +278,19 @@ Errors will bubble up to the nearest parent error boundary. This allows for gran
278278
height="687"
279279
/>
280280
281-
For component-level error recovery, the [`unstable_catchError`](/docs/app/api-reference/functions/catchError) function lets you create error boundaries that can wrap any part of your component tree:
281+
For component-level error recovery, the [`catchError`](/docs/app/api-reference/functions/catchError) function lets you create error boundaries that can wrap any part of your component tree:
282282
283283
```tsx filename="app/custom-error-boundary.tsx" switcher
284284
'use client'
285285

286-
import { unstable_catchError as catchError, type ErrorInfo } from 'next/error'
286+
import { catchError, type ErrorInfo } from 'next/error'
287287

288-
function ErrorFallback(
289-
props: { title: string },
290-
{ error, unstable_retry }: ErrorInfo
291-
) {
288+
function ErrorFallback(props: { title: string }, { error, retry }: ErrorInfo) {
292289
return (
293290
<div>
294291
<h2>{props.title}</h2>
295292
<p>{error.message}</p>
296-
<button onClick={() => unstable_retry()}>Try again</button>
293+
<button onClick={() => retry()}>Try again</button>
297294
</div>
298295
)
299296
}
@@ -304,14 +301,14 @@ export default catchError(ErrorFallback)
304301
```jsx filename="app/custom-error-boundary.js" switcher
305302
'use client'
306303

307-
import { unstable_catchError as catchError } from 'next/error'
304+
import { catchError } from 'next/error'
308305

309-
function ErrorFallback(props, { error, unstable_retry }) {
306+
function ErrorFallback(props, { error, retry }) {
310307
return (
311308
<div>
312309
<h2>{props.title}</h2>
313310
<p>{error.message}</p>
314-
<button onClick={() => unstable_retry()}>Try again</button>
311+
<button onClick={() => retry()}>Try again</button>
315312
</div>
316313
)
317314
}
@@ -404,17 +401,17 @@ While less common, you can handle errors in the root layout using the [`global-e
404401

405402
export default function GlobalError({
406403
error,
407-
unstable_retry,
404+
retry,
408405
}: {
409406
error: Error & { digest?: string }
410-
unstable_retry: () => void
407+
retry: () => void
411408
}) {
412409
return (
413410
// global-error must include html and body tags
414411
<html>
415412
<body>
416413
<h2>Something went wrong!</h2>
417-
<button onClick={() => unstable_retry()}>Try again</button>
414+
<button onClick={() => retry()}>Try again</button>
418415
</body>
419416
</html>
420417
)
@@ -424,13 +421,13 @@ export default function GlobalError({
424421
```jsx filename="app/global-error.js" switcher
425422
'use client' // Error boundaries must be Client Components
426423

427-
export default function GlobalError({ error, unstable_retry }) {
424+
export default function GlobalError({ error, retry }) {
428425
return (
429426
// global-error must include html and body tags
430427
<html>
431428
<body>
432429
<h2>Something went wrong!</h2>
433-
<button onClick={() => unstable_retry()}>Try again</button>
430+
<button onClick={() => retry()}>Try again</button>
434431
</body>
435432
</html>
436433
)

docs/01-app/03-api-reference/03-file-conventions/error.mdx

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ import { useEffect } from 'react'
2424

2525
export default function Error({
2626
error,
27-
unstable_retry,
27+
retry,
2828
}: {
2929
error: Error & { digest?: string }
30-
unstable_retry: () => void
30+
retry: () => void
3131
}) {
3232
useEffect(() => {
3333
// Log the error to an error reporting service
@@ -40,7 +40,7 @@ export default function Error({
4040
<button
4141
onClick={
4242
// Attempt to recover by re-fetching and re-rendering the segment
43-
() => unstable_retry()
43+
() => retry()
4444
}
4545
>
4646
Try again
@@ -55,7 +55,7 @@ export default function Error({
5555

5656
import { useEffect } from 'react'
5757

58-
export default function Error({ error, unstable_retry }) {
58+
export default function Error({ error, retry }) {
5959
useEffect(() => {
6060
// Log the error to an error reporting service
6161
console.error(error)
@@ -67,7 +67,7 @@ export default function Error({ error, unstable_retry }) {
6767
<button
6868
onClick={
6969
// Attempt to recover by re-fetching and re-rendering the segment
70-
() => unstable_retry()
70+
() => retry()
7171
}
7272
>
7373
Try again
@@ -91,7 +91,7 @@ export default function Error({ error, unstable_retry }) {
9191
>
9292
> - The [React DevTools](https://react.dev/learn/react-developer-tools) allow you to toggle error boundaries to test error states.
9393
> - If you want errors to bubble up to the parent error boundary, you can `throw` when rendering the `error` component.
94-
> - For component-level error recovery that aren't tied to route segments like [`error.js`](/docs/app/api-reference/file-conventions/error), use the [`unstable_catchError`](/docs/app/api-reference/functions/catchError) function.
94+
> - For component-level error recovery that aren't tied to route segments like [`error.js`](/docs/app/api-reference/file-conventions/error), use the [`catchError`](/docs/app/api-reference/functions/catchError) function.
9595
9696
In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `error.js` wraps `loading.js`, `not-found.js`, `page.js`, and nested `layout.js` files in a React error boundary. It does **not** wrap the `layout.js` or `template.js` above it in the same segment. To handle errors in the root layout, use [`global-error.js`](/docs/app/api-reference/file-conventions/error#global-error).
9797

@@ -114,26 +114,26 @@ An instance of an [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Re
114114

115115
An automatically generated hash of the error thrown. It can be used to match the corresponding error in server-side logs.
116116

117-
#### `unstable_retry`
117+
#### `retry`
118118

119119
The cause of an error can sometimes be temporary. In these cases, trying again might resolve the issue.
120120

121-
An error component can use the `unstable_retry()` function to prompt the user to attempt to recover from the error. When executed, the function will try to re-fetch and re-render the error boundary's children. If successful, the fallback error component is replaced with the result of the re-render.
121+
An error component can use the `retry()` function to prompt the user to attempt to recover from the error. When executed, the function will try to re-fetch and re-render the error boundary's children. If successful, the fallback error component is replaced with the result of the re-render.
122122

123123
```tsx filename="app/dashboard/error.tsx" switcher
124124
'use client' // Error boundaries must be Client Components
125125

126126
export default function Error({
127127
error,
128-
unstable_retry,
128+
retry,
129129
}: {
130130
error: Error & { digest?: string }
131-
unstable_retry: () => void
131+
retry: () => void
132132
}) {
133133
return (
134134
<div>
135135
<h2>Something went wrong!</h2>
136-
<button onClick={() => unstable_retry()}>Try again</button>
136+
<button onClick={() => retry()}>Try again</button>
137137
</div>
138138
)
139139
}
@@ -142,19 +142,19 @@ export default function Error({
142142
```jsx filename="app/dashboard/error.js" switcher
143143
'use client' // Error boundaries must be Client Components
144144

145-
export default function Error({ error, unstable_retry }) {
145+
export default function Error({ error, retry }) {
146146
return (
147147
<div>
148148
<h2>Something went wrong!</h2>
149-
<button onClick={() => unstable_retry()}>Try again</button>
149+
<button onClick={() => retry()}>Try again</button>
150150
</div>
151151
)
152152
}
153153
```
154154

155155
#### `reset`
156156

157-
In most cases, you should use [`unstable_retry()`](#unstable_retry) instead. However, if you have a specific reason to clear the error state and re-render the error boundary's children without re-fetching the contents, you can use the `reset()` function.
157+
In most cases, you should use [`retry()`](#retry) instead. However, if you have a specific reason to clear the error state and re-render the error boundary's children without re-fetching the contents, you can use the `reset()` function.
158158

159159
## Examples
160160

@@ -169,17 +169,17 @@ While less common, you can handle errors in the root layout or template using `g
169169

170170
export default function GlobalError({
171171
error,
172-
unstable_retry,
172+
retry,
173173
}: {
174174
error: Error & { digest?: string }
175-
unstable_retry: () => void
175+
retry: () => void
176176
}) {
177177
return (
178178
// global-error must include html and body tags
179179
<html>
180180
<body>
181181
<h2>Something went wrong!</h2>
182-
<button onClick={() => unstable_retry()}>Try again</button>
182+
<button onClick={() => retry()}>Try again</button>
183183
</body>
184184
</html>
185185
)
@@ -189,13 +189,13 @@ export default function GlobalError({
189189
```jsx filename="app/global-error.js" switcher
190190
'use client' // Error boundaries must be Client Components
191191

192-
export default function GlobalError({ error, unstable_retry }) {
192+
export default function GlobalError({ error, retry }) {
193193
return (
194194
// global-error must include html and body tags
195195
<html>
196196
<body>
197197
<h2>Something went wrong!</h2>
198-
<button onClick={() => unstable_retry()}>Try again</button>
198+
<button onClick={() => retry()}>Try again</button>
199199
</body>
200200
</html>
201201
)
@@ -326,6 +326,7 @@ export default GracefullyDegradingErrorBoundary
326326
327327
| Version | Changes |
328328
| --------- | ------------------------------------------- |
329+
| `v16.3.0` | `retry` prop became stable. |
329330
| `v16.2.0` | `unstable_retry` prop added. |
330331
| `v15.2.0` | Also display `global-error` in development. |
331332
| `v13.1.0` | `global-error` introduced. |

0 commit comments

Comments
 (0)