Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/pages/reference/useQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const {
refetchOnWindowFocus,
retry,
retryDelay,
select
staleTime,
structuralSharing,
suspense,
Expand Down
26 changes: 26 additions & 0 deletions examples/optimistic-updates-typescript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

yarn.lock
package-lock.json

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
6 changes: 6 additions & 0 deletions examples/optimistic-updates-typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Example

To run this example:

- `npm install` or `yarn`
- `npm run dev` or `yarn dev`
2 changes: 2 additions & 0 deletions examples/optimistic-updates-typescript/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
48 changes: 48 additions & 0 deletions examples/optimistic-updates-typescript/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const Module = require('module')
const path = require('path')
const resolveFrom = require('resolve-from')

const node_modules = path.resolve(__dirname, 'node_modules')

const originalRequire = Module.prototype.require

// The following ensures that there is always only a single (and same)
// copy of React in an app at any given moment.
Module.prototype.require = function (modulePath) {
// Only redirect resolutions to non-relative and non-absolute modules
if (
['/react/', '/react-dom/', '/react-query/'].some(d => {
try {
return require.resolve(modulePath).includes(d)
} catch (err) {
return false
}
})
) {
try {
modulePath = resolveFrom(node_modules, modulePath)
} catch (err) {
//
}
}

return originalRequire.call(this, modulePath)
}

module.exports = {
webpack: config => {
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
react$: resolveFrom(path.resolve('node_modules'), 'react'),
'react-query$': resolveFrom(
path.resolve('node_modules'),
'react-query'
),
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
},
}
return config
},
}
21 changes: 21 additions & 0 deletions examples/optimistic-updates-typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "basic",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"axios": "^0.19.2",
"isomorphic-unfetch": "3.0.0",
"next": "9.2.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-query": "^3.2.0-beta.32",
"react-query-devtools": "^3.0.0-beta.1",
"typescript": "^4.1.2"
},
"scripts": {
"dev": "next",
"start": "next start",
"build": "next build"
}
}
27 changes: 27 additions & 0 deletions examples/optimistic-updates-typescript/pages/api/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const items = []

export default async (req, res) => {
await new Promise(r => setTimeout(r, 1000))

if (req.method === 'POST') {
const { text } = req.body

// sometimes it will fail, this will cause a regression on the UI

if (Math.random() > 0.7) {
res.status(500)
res.json({ message: 'Could not add item!' })
return
}

const newTodo = { id: Math.random().toString(), text: text.toUpperCase() }
items.push(newTodo)
res.json(newTodo)
return
} else {
res.json({
ts: Date.now(),
items,
})
}
}
145 changes: 145 additions & 0 deletions examples/optimistic-updates-typescript/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import * as React from 'react'
import axios, { AxiosError } from 'axios'

import {
useQuery,
useQueryClient,
useMutation,
QueryClient,
QueryClientProvider,
UseQueryOptions,
} from 'react-query'
import { ReactQueryDevtools } from 'react-query-devtools'

const client = new QueryClient()

export default function App() {
return (
<QueryClientProvider client={client}>
<Example />
<TodoCounter />
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
)
}

type Todos = {
items: readonly {
id: string
text: string
}[]
ts: number
}

async function fetchTodos(): Promise<Todos> {
const res = await axios.get('/api/data')
return res.data
}

function useTodos<TData = Todos>(
options?: UseQueryOptions<TData, AxiosError, Todos>
) {
return useQuery('todos', fetchTodos, options)
}

function TodoCounter() {
// subscribe only to changes in the 'data' prop, which will be the
// amount of todos because of the select function
const counterQuery = useTodos({
select: data => data.items.length,
notifyOnChangeProps: ['data'],
})

React.useEffect(() => {
console.log('rendering counter')
})

return <div>TodoCounter: {counterQuery.data ?? 0}</div>
}

function Example() {
const queryClient = useQueryClient()
const [text, setText] = React.useState('')
const { isFetching, ...queryInfo } = useTodos()

const addTodoMutation = useMutation(
newTodo => axios.post('/api/data', { text: newTodo }),
{
// When mutate is called:
onMutate: async (newTodo: string) => {
setText('')
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries('todos')

// Snapshot the previous value
const previousTodos = queryClient.getQueryData<Todos>('todos')

// Optimistically update to the new value
if (previousTodos) {
queryClient.setQueryData<Todos>('todos', {
...previousTodos,
items: [
...previousTodos.items,
{ id: Math.random().toString(), text: newTodo },
],
})
}

return { previousTodos }
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (err, variables, context) => {
if (context?.previousTodos) {
queryClient.setQueryData<Todos>('todos', context.previousTodos)
}
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries('todos')
},
}
)

return (
<div>
<p>
In this example, new items can be created using a mutation. The new item
will be optimistically added to the list in hopes that the server
accepts the item. If it does, the list is refetched with the true items
from the list. Every now and then, the mutation may fail though. When
that happens, the previous list of items is restored and the list is
again refetched from the server.
</p>
<form
onSubmit={e => {
e.preventDefault()
addTodoMutation.mutate(text)
}}
>
<input
type="text"
onChange={event => setText(event.target.value)}
value={text}
/>
<button disabled={addTodoMutation.isLoading}>Create</button>
</form>
<br />
{queryInfo.isSuccess && (
<>
<div>
{/* The type of queryInfo.data will be narrowed because we check for isSuccess first */}
Updated At: {new Date(queryInfo.data.ts).toLocaleTimeString()}
</div>
<ul>
{queryInfo.data.items.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
{isFetching && <div>Updating in background...</div>}
</>
)}
{queryInfo.isLoading && 'Loading'}
{queryInfo.error?.message}
</div>
)
}
26 changes: 26 additions & 0 deletions examples/optimistic-updates-typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"include": [
"./pages/**/*"
],
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"lib": [
"dom",
"es2015"
],
"jsx": "preserve",
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true
},
"exclude": [
"node_modules"
]
}