snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
33
examples/react/chat/src/chat.ts
Normal file
33
examples/react/chat/src/chat.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
queryOptions,
|
||||
experimental_streamedQuery as streamedQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
const answers = [
|
||||
"I'm just an example chat, I can't really answer any questions :(".split(' '),
|
||||
'TanStack is great. Would you like to know more?'.split(' '),
|
||||
]
|
||||
|
||||
function chatAnswer(_question: string) {
|
||||
return {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
const answer = answers[Math.floor(Math.random() * answers.length)]
|
||||
let index = 0
|
||||
while (index < answer.length) {
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, 100 + Math.random() * 300),
|
||||
)
|
||||
yield answer[index++]
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const chatQueryOptions = (question: string) =>
|
||||
queryOptions({
|
||||
queryKey: ['chat', question],
|
||||
queryFn: streamedQuery({
|
||||
streamFn: () => chatAnswer(question),
|
||||
}),
|
||||
staleTime: Infinity,
|
||||
})
|
||||
101
examples/react/chat/src/index.tsx
Normal file
101
examples/react/chat/src/index.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import {
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
|
||||
import './style.css'
|
||||
import { useState } from 'react'
|
||||
import { chatQueryOptions } from './chat'
|
||||
import { Message } from './message'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ReactQueryDevtools />
|
||||
<Example />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function ChatMessage({ question }: { question: string }) {
|
||||
const { error, data = [], isFetching } = useQuery(chatQueryOptions(question))
|
||||
|
||||
if (error) return 'An error has occurred: ' + error.message
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Message message={{ content: question, isQuestion: true }} />
|
||||
<Message
|
||||
inProgress={isFetching}
|
||||
message={{ content: data.join(' '), isQuestion: false }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Example() {
|
||||
const [questions, setQuestions] = useState<Array<string>>([])
|
||||
const [currentQuestion, setCurrentQuestion] = useState('')
|
||||
|
||||
const submitMessage = () => {
|
||||
setQuestions([...questions, currentQuestion])
|
||||
setCurrentQuestion('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen max-w-3xl mx-auto p-4">
|
||||
<h1 className="text-3xl font-bold text-gray-800">
|
||||
TanStack Chat Example
|
||||
</h1>
|
||||
<div className="overflow-y-auto mb-4 space-y-4">
|
||||
{questions.map((question) => (
|
||||
<ChatMessage key={question} question={question} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
className="flex-1 p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-100"
|
||||
value={currentQuestion}
|
||||
onChange={(e) => setCurrentQuestion(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
submitMessage()
|
||||
}
|
||||
}}
|
||||
placeholder="Type your message..."
|
||||
/>
|
||||
<button
|
||||
onClick={submitMessage}
|
||||
disabled={!currentQuestion.trim()}
|
||||
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold px-4 py-2 rounded-2xl shadow-md transition"
|
||||
>
|
||||
<span>Send</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z" />
|
||||
<path d="m21.854 2.147-10.94 10.939" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
if (!rootElement) throw new Error('Missing #root element')
|
||||
ReactDOM.createRoot(rootElement).render(<App />)
|
||||
24
examples/react/chat/src/message.tsx
Normal file
24
examples/react/chat/src/message.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
export function Message({
|
||||
inProgress,
|
||||
message,
|
||||
}: {
|
||||
inProgress?: boolean
|
||||
message: { content: string; isQuestion: boolean }
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`flex ${message.isQuestion ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-[80%] rounded-lg p-3 ${
|
||||
message.isQuestion
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-200 text-gray-800'
|
||||
}`}
|
||||
>
|
||||
{message.content}
|
||||
{inProgress ? '...' : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
examples/react/chat/src/style.css
Normal file
1
examples/react/chat/src/style.css
Normal file
@@ -0,0 +1 @@
|
||||
@import 'tailwindcss';
|
||||
Reference in New Issue
Block a user