Skip to content

Commit c703e6c

Browse files
authored
feat: add a warning for invalid arguments with suspense mode (vercel#1402)
1 parent 321a0c1 commit c703e6c

File tree

2 files changed

+52
-5
lines changed

2 files changed

+52
-5
lines changed

src/use-swr.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export const useSWRHandler = <Data = any, Error = any>(
7878
const data = isUndefined(cached) ? fallback : cached
7979
const error = cache.get(keyErr)
8080

81+
if (suspense && (!key || !fn)) {
82+
throw new Error('useSWR requires either key or fetcher with suspense mode')
83+
}
84+
8185
// A revalidation must be triggered when mounted if:
8286
// - `revalidateOnMount` is explicitly set to `true`.
8387
// - Suspense mode and there's stale data for the initial render.

test/use-swr-suspense.test.tsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import React, { ReactNode, Suspense, useEffect, useState } from 'react'
33
import useSWR, { mutate } from 'swr'
44
import { createResponse, sleep } from './utils'
55

6-
class ErrorBoundary extends React.Component<{ fallback: ReactNode }> {
7-
state = { hasError: false }
8-
static getDerivedStateFromError() {
6+
class ErrorBoundary extends React.Component<{ fallback?: ReactNode }> {
7+
state = { hasError: false, message: null }
8+
static getDerivedStateFromError(error: Error) {
99
return {
10-
hasError: true
10+
hasError: true,
11+
message: error.message
1112
}
1213
}
1314
render() {
1415
if (this.state.hasError) {
15-
return this.props.fallback
16+
return this.props.fallback || this.state.message
1617
}
1718
return this.props.children
1819
}
@@ -258,4 +259,46 @@ describe('useSWR - suspense', () => {
258259
expect(startRenderCount).toBe(2) // fallback + data
259260
expect(renderCount).toBe(1) // data
260261
})
262+
263+
it('should throw an error if key is a falsy value', async () => {
264+
// eslint-disable-next-line @typescript-eslint/no-empty-function
265+
jest.spyOn(console, 'error').mockImplementation(() => {})
266+
267+
function Page() {
268+
const { data } = useSWR(null, () => createResponse('SWR'), {
269+
suspense: true
270+
})
271+
return <div>hello, {data}</div>
272+
}
273+
render(
274+
<ErrorBoundary>
275+
<Suspense fallback={<div>fallback</div>}>
276+
<Page />
277+
</Suspense>
278+
</ErrorBoundary>
279+
)
280+
281+
screen.getByText('useSWR requires either key or fetcher with suspense mode')
282+
})
283+
284+
it('should throw an error if fetch is null', async () => {
285+
// eslint-disable-next-line @typescript-eslint/no-empty-function
286+
jest.spyOn(console, 'error').mockImplementation(() => {})
287+
288+
function Page() {
289+
const { data } = useSWR('suspense-11', null, {
290+
suspense: true
291+
})
292+
return <div>hello, {data}</div>
293+
}
294+
render(
295+
<ErrorBoundary>
296+
<Suspense fallback={<div>fallback</div>}>
297+
<Page />
298+
</Suspense>
299+
</ErrorBoundary>
300+
)
301+
302+
screen.getByText('useSWR requires either key or fetcher with suspense mode')
303+
})
261304
})

0 commit comments

Comments
 (0)