-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(rtk-query/react): add useUnstable_SuspenseQuery
hook
#2149
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
00251fb
feat(rtk-query/react): add useUnstable_SuspenseQuery & suspense example
FaberVitale f0818a9
chore: add examples/query/react/suspense to sandbox ci
FaberVitale a6b7f36
fix: skip unnecessary render if there's no pending promise
FaberVitale f2d0ec6
chore: improve slightly fallback suspense UI in suspense example
FaberVitale File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
SKIP_PREFLIGHT_CHECK=true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "@examples-query-react/suspense", | ||
"private": true, | ||
"version": "1.0.0", | ||
"description": "", | ||
"keywords": [], | ||
"main": "src/index.tsx", | ||
"dependencies": { | ||
"@reduxjs/toolkit": "^1.6.0-rc.1", | ||
"react": "17.0.0", | ||
"react-dom": "17.0.0", | ||
"react-error-boundary": "3.1.4", | ||
"react-redux": "7.2.2", | ||
"react-scripts": "4.0.2" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "17.0.0", | ||
"@types/react-dom": "17.0.0", | ||
"@types/react-redux": "7.1.9", | ||
"typescript": "~4.2.4" | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"react-app" | ||
], | ||
"rules": { | ||
"react/react-in-jsx-scope": "off" | ||
} | ||
}, | ||
"scripts": { | ||
"start": "react-scripts start", | ||
"build": "react-scripts build" | ||
}, | ||
"browserslist": [ | ||
">0.2%", | ||
"not dead", | ||
"not ie <= 11", | ||
"not op_mini all" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
<meta name="theme-color" content="#000000"> | ||
<!-- | ||
manifest.json provides metadata used when your web app is added to the | ||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ | ||
--> | ||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> | ||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | ||
<!-- | ||
Notice the use of %PUBLIC_URL% in the tags above. | ||
It will be replaced with the URL of the `public` folder during the build. | ||
Only files inside the `public` folder can be referenced from the HTML. | ||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | ||
work correctly both with client-side routing and a non-root public URL. | ||
Learn how to configure a non-root public URL by running `npm run build`. | ||
--> | ||
<title>React App</title> | ||
</head> | ||
|
||
<body> | ||
<noscript> | ||
You need to enable JavaScript to run this app. | ||
</noscript> | ||
<div id="root"></div> | ||
<!-- | ||
This HTML file is a template. | ||
If you open it directly in the browser, you will see an empty page. | ||
You can add webfonts, meta tags, or analytics to this file. | ||
The build step will place the bundled scripts into the <body> tag. | ||
To begin the development, run `npm start` or `yarn start`. | ||
To create a production bundle, use `npm run build` or `yarn build`. | ||
--> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"short_name": "RTK Query Polling Example", | ||
"name": "Polling Example", | ||
"start_url": ".", | ||
"display": "standalone", | ||
"theme_color": "#000000", | ||
"background_color": "#ffffff" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import * as React from 'react' | ||
import { POKEMON_NAMES } from './pokemon.data' | ||
import './styles.css' | ||
import { SuspendedPokemon, SuspendedPokemonProps } from './SuspendedPokemon' | ||
|
||
const getRandomPokemonName = () => | ||
POKEMON_NAMES[Math.floor(Math.random() * POKEMON_NAMES.length)] | ||
|
||
export default function App() { | ||
const [pokemonConf, setPokemonConf] = React.useState<SuspendedPokemonProps[]>( | ||
[{ name: 'bulbasaur', suspendOnRefetch: false, throwOnIntialRender: false }] | ||
) | ||
|
||
return ( | ||
<div className="App"> | ||
<div> | ||
<form | ||
action="#" | ||
onSubmit={(evt) => { | ||
evt.preventDefault() | ||
|
||
const formValues = new FormData(evt.currentTarget) | ||
|
||
setPokemonConf((prev) => [ | ||
...prev, | ||
{ | ||
name: Boolean(formValues.get('addBulbasaur')) | ||
? 'bulbasaur' | ||
: getRandomPokemonName(), | ||
suspendOnRefetch: Boolean(formValues.get('suspendOnRefetch')), | ||
throwOnIntialRender: Boolean( | ||
formValues.get('throwOnIntialRender') | ||
), | ||
}, | ||
]) | ||
}} | ||
> | ||
<label htmlFor="suspendOnRefetch"> | ||
suspendOnRefetch | ||
<input | ||
type="checkbox" | ||
name="suspendOnRefetch" | ||
id="suspendOnRefetch" | ||
/> | ||
</label> | ||
<label htmlFor="addBulbasaur"> | ||
addBulbasaur | ||
<input type="checkbox" name="addBulbasaur" id="addBulbasaur" /> | ||
</label> | ||
<label htmlFor="throwOnIntialRender"> | ||
throwOnIntialRender | ||
<input | ||
type="checkbox" | ||
name="throwOnIntialRender" | ||
id="throwOnIntialRender" | ||
/> | ||
</label> | ||
<button>Add pokemon</button> | ||
</form> | ||
</div> | ||
<div className="pokemon-list"> | ||
{pokemonConf.map((suspendedPokemonProps, index) => ( | ||
<SuspendedPokemon key={index} {...suspendedPokemonProps} /> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import * as React from 'react' | ||
import { pokemonApi } from './services/pokemon' | ||
import type { PokemonName } from './pokemon.data' | ||
|
||
const intervalOptions = [ | ||
{ label: 'Off', value: 0 }, | ||
{ label: '20s', value: 10000 }, | ||
{ label: '1m', value: 60000 }, | ||
] | ||
|
||
const getRandomIntervalValue = () => | ||
intervalOptions[Math.floor(Math.random() * intervalOptions.length)].value | ||
|
||
export const Pokemon = ({ | ||
name, | ||
suspendOnRefetch = false, | ||
}: { | ||
name: PokemonName | ||
suspendOnRefetch?: boolean | ||
}) => { | ||
const [pollingInterval, setPollingInterval] = React.useState( | ||
getRandomIntervalValue() | ||
) | ||
|
||
const { data, isFetching, refetch } = | ||
pokemonApi.endpoints.getPokemonByName.useUnstable_SuspenseQuery(name, { | ||
pollingInterval, | ||
suspendOnRefetch, | ||
}) | ||
|
||
if (!data) { | ||
return ( | ||
<section> | ||
<h3>{name}</h3> | ||
<p>No data!</p> | ||
</section> | ||
) | ||
} | ||
|
||
return ( | ||
<section> | ||
<h3>{data.species.name}</h3> | ||
<div style={{ minWidth: 96, minHeight: 96 }}> | ||
<img | ||
src={data.sprites.front_shiny} | ||
alt={data.species.name} | ||
style={{ ...(isFetching ? { opacity: 0.3 } : {}) }} | ||
/> | ||
</div> | ||
<div> | ||
<label style={{ display: 'block' }}>Polling interval</label> | ||
<select | ||
value={pollingInterval} | ||
onChange={({ target: { value } }) => | ||
setPollingInterval(Number(value)) | ||
} | ||
> | ||
{intervalOptions.map(({ label, value }) => ( | ||
<option key={value} value={value}> | ||
{label} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<div> | ||
<p>suspendOnRefetch: {String(suspendOnRefetch)}</p> | ||
<button onClick={refetch} disabled={isFetching}> | ||
{isFetching ? 'Loading' : 'Manually refetch'} | ||
</button> | ||
</div> | ||
</section> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { memo } from 'react'; | ||
import { Suspense, useState } from 'react' | ||
import { Pokemon } from './Pokemon' | ||
import { PokemonName } from './pokemon.data' | ||
import { ErrorBoundary } from 'react-error-boundary' | ||
|
||
export interface SuspendedPokemonProps { | ||
name: PokemonName | ||
suspendOnRefetch: boolean | ||
throwOnIntialRender: boolean | ||
} | ||
|
||
function BuggyComponent({ errorCount, name }:Pick<SuspendedPokemonProps, 'name'> & { errorCount: number }) { | ||
if(!errorCount) { | ||
throw new Error(`error while rendering: ${name}, errorCount ${errorCount}`); | ||
} | ||
|
||
return <></> | ||
} | ||
|
||
export const SuspendedPokemon = memo(function SuspendedPokemon({ | ||
name, | ||
suspendOnRefetch, | ||
throwOnIntialRender, | ||
}: SuspendedPokemonProps) { | ||
const [errorCount, setErrorCount] = useState(0) | ||
|
||
return ( | ||
<div> | ||
<ErrorBoundary | ||
onReset={() => setErrorCount((n) => n + 1)} | ||
fallbackRender={({ resetErrorBoundary, error }) => { | ||
return ( | ||
<section> | ||
<h3>render {name} error</h3> | ||
<p>{String(error)}</p> | ||
<div> | ||
<button type="button" onClick={resetErrorBoundary}> | ||
reset error boundary | ||
</button> | ||
</div> | ||
</section> | ||
) | ||
}} | ||
> | ||
{throwOnIntialRender && <BuggyComponent name={name} errorCount={errorCount} />} | ||
<Suspense | ||
fallback={ | ||
<div className={'suspense-fallback-wrapper'}> | ||
Suspense fallback UI.<br /> | ||
Loading pokemon {name} | ||
</div> | ||
} | ||
> | ||
<Pokemon name={name} suspendOnRefetch={suspendOnRefetch} /> | ||
</Suspense> | ||
</ErrorBoundary> | ||
</div> | ||
) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { render } from 'react-dom' | ||
import { Provider } from 'react-redux' | ||
|
||
import App from './App' | ||
import { store } from './store' | ||
|
||
const rootElement = document.getElementById('root') | ||
render( | ||
<Provider store={store}> | ||
<App /> | ||
</Provider>, | ||
rootElement | ||
) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a way to type
isFetching
as void or remove it from the return value ifsuspendOnRefetch
is true?if
suspendOnRefetch
istrue
,isFetching
is always going to befalse
when accessed in user codeThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can play with
ts
types in order to haveisFetching
be always true ifsuspendOnRefetch
is set totrue
but,no I don't think that we should remove
isFetching
from the response.